Source to iokit/Families/IOStorage/IODrive.cpp
/*
* Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* The contents of this file constitute Original Code as defined in and
* are subject to the Apple Public Source License Version 1.1 (the
* "License"). You may not use this file except in compliance with the
* License. Please obtain a copy of the License at
* http://www.apple.com/publicsource and read it before using this file.
*
* This Original Code and all software distributed under the License are
* distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#include <IOKit/assert.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/storage/IODrive.h>
#define super IOStorage
OSDefineMetaClass(IODrive, IOStorage)
OSDefineAbstractStructors(IODrive, IOStorage)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const UInt32 kPollerInterval = 1000; // (ms, 1 second)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool IODrive::init(OSDictionary * properties = 0)
{
//
// Initialize this object's minimal state.
//
if (super::init(properties) == false) return false;
_deblockerLockForRMWs = IOLockAlloc();
_openClients = OSSet::withCapacity(2);
for (unsigned index = 0; index < kStatisticsCount; index++)
_statistics[index] = OSNumber::withNumber(0ULL, 64);
if (_deblockerLockForRMWs == 0 || _openClients == 0) return false;
for (unsigned index = 0; index < kStatisticsCount; index++)
if (_statistics[index] == 0) return false;
IOLockInit(_deblockerLockForRMWs);
//
// Create the standard drive registry properties.
//
OSDictionary * statistics = OSDictionary::withCapacity(kStatisticsCount);
if (statistics == 0) return false;
statistics->setObject( kIODriveStatisticsBytesRead,
_statistics[kStatisticsBytesRead] );
statistics->setObject( kIODriveStatisticsBytesWritten,
_statistics[kStatisticsBytesWritten] );
statistics->setObject( kIODriveStatisticsReadErrors,
_statistics[kStatisticsReadErrors] );
statistics->setObject( kIODriveStatisticsWriteErrors,
_statistics[kStatisticsWriteErrors] );
statistics->setObject( kIODriveStatisticsLatentReadTime,
_statistics[kStatisticsLatentReadTime] );
statistics->setObject( kIODriveStatisticsLatentWriteTime,
_statistics[kStatisticsLatentWriteTime] );
statistics->setObject( kIODriveStatisticsReads,
_statistics[kStatisticsReads] );
statistics->setObject( kIODriveStatisticsWrites,
_statistics[kStatisticsWrites] );
statistics->setObject( kIODriveStatisticsReadRetries,
_statistics[kStatisticsReadRetries] );
statistics->setObject( kIODriveStatisticsWriteRetries,
_statistics[kStatisticsWriteRetries] );
statistics->setObject( kIODriveStatisticsTotalReadTime,
_statistics[kStatisticsTotalReadTime] );
statistics->setObject( kIODriveStatisticsTotalWriteTime,
_statistics[kStatisticsTotalWriteTime] );
setProperty(kIODriveStatistics, statistics);
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool IODrive::start(IOService * provider)
{
//
// This method is called once we have been attached to the provider object.
//
// Instruct our subclass to prepare the drive for operation.
if (handleStart(provider) == false) return false;
// Initiate the poller mechanism if it is required.
if (isMediaEjectable() && isMediaPollRequired() && !isMediaPollExpensive())
{
lockForArbitration(); // (disable opens/closes; a recursive lock)
if (!isOpen() && !isInactive())
schedulePoller(); // (schedule the poller, increments retain)
unlockForArbitration(); // (enable opens/closes; a recursive lock)
}
// Register this object so it can be found via notification requests. It is
// not being registered to have I/O Kit attempt to have drivers match on it,
// which is the reason most other services are registered -- that's not the
// intention of this registerService call.
registerService();
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::stop(IOService * provider)
{
//
// This method is called before we are detached from the provider object.
//
// Halt the poller mechanism if it is required.
if (isMediaEjectable() && isMediaPollRequired() && !isMediaPollExpensive())
unschedulePoller(); // (unschedule the poller)
// Instruct our subclass to stop the drive.
handleStop(provider);
super::stop(provider);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::free()
{
//
// Free all of this object's outstanding resources.
//
if (_deblockerLockForRMWs) IOLockFree(_deblockerLockForRMWs);
if (_openClients) _openClients->release();
for (unsigned index = 0; index < kStatisticsCount; index++)
if (_statistics[index]) _statistics[index]->release();
super::free();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool IODrive::handleOpen(IOService * client,
IOOptionBits options,
void * argument)
{
//
// The handleOpen method grants or denies permission to access this object
// to an interested client. The argument is an IOStorageAccess value that
// specifies the level of access desired -- reader or reader-writer.
//
// This method can be invoked to upgrade or downgrade the access level for
// an existing client as well. The previous access level will prevail for
// upgrades that fail, of course. A downgrade should never fail. If the
// new access level should be the same as the old for a given client, this
// method will do nothing and return success. In all cases, one, singular
// close-per-client is expected for all opens-per-client received.
//
// We are guaranteed that no other opens or closes will be processed until
// we make our decision, change our state, and return from this method.
//
IOStorageAccess access = (IOStorageAccess) (int) argument;
assert(client);
assert(access != kAccessNone);
// Handle the first open on removable media in a special case.
if (isMediaEjectable() && _openClients->getCount() == 0)
{
// Halt the poller if it is active and this is the drive's first open.
if (isMediaPollRequired() && !isMediaPollExpensive())
unschedulePoller(); // (unschedule the poller)
// Lock down the media while we have opens on this drive object. The
// arbitration lock is held during the operation -- however we assume
// the extra length of time will not be an issue for now.
if (lockMedia(true) != kIOReturnSuccess)
IOLog("%s: Unable to lock down removable media.\n", getName());
}
// Process the open.
_openClients->setObject(client); // (works for up/downgrade case)
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool IODrive::handleIsOpen(const IOService * client) const
{
//
// The handleIsOpen method determines whether the specified client, or any
// client if none is specificed, presently has an open on this object.
//
// We are guaranteed that no other opens or closes will be processed until
// we return from this method.
//
if (client)
return _openClients->containsObject(client);
else
return (_openClients->getCount() != 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::handleClose(IOService * client, IOOptionBits options)
{
//
// The handleClose method drops the incoming client's access to this object.
//
// We are guaranteed that no other opens or closes will be processed until
// we change our state and return from this method.
//
assert(client);
// Process the close.
_openClients->removeObject(client);
// Handle the last close on removable media in a special case.
if (isMediaEjectable() && _openClients->getCount() == 0)
{
// Unlock the media in the drive. The arbitration lock is held during
// the operation -- however we assume the extra length of time will not
// be an issue for now.
if (lockMedia(false) != kIOReturnSuccess)
IOLog("%s: Unable to unlock removable media.\n", getName());
// Reactivate the poller.
if (isMediaPollRequired() && !isMediaPollExpensive() && !isInactive())
schedulePoller(); // (schedule the poller, increments retain)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::read(IOService * /* client */,
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageCompletion completion)
{
//
// Read data from the storage object at the specified byte offset into the
// specified buffer, asynchronously. When the read completes, the caller
// will be notified via the specified completion action.
//
// The buffer will be retained for the duration of the read.
//
// Note that the read passes through several other methods before being
// passed to executeRequest. The first is deblockRequest, which aligns
// the request with the media's block boundaries; the second is prepare-
// Request which prepares the memory involved in the transfer (involves
// wiring, virtual-to-physical mapping, and breakup of the memory range
// based on the controller's and/or drive's constraints).
//
UInt64 mediaBlockSize = getMediaBlockSize();
// State our assumptions.
assert(buffer->getDirection() == kIODirectionIn);
// If the request is aligned with the media's block boundaries, we can
// short-circuit the deblocker and call prepareRequest directly.
if ( (byteStart % mediaBlockSize) == 0 &&
(buffer->getLength() % mediaBlockSize) == 0 )
{
prepareRequest(byteStart, buffer, completion);
return;
}
deblockRequest(byteStart, buffer, completion);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::write(IOService * /* client */,
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageCompletion completion)
{
//
// Write data into the storage object at the specified byte offset from the
// specified buffer, asynchronously. When the write completes, the caller
// will be notified via the specified completion action.
//
// The buffer will be retained for the duration of the write.
//
// Note that the write passes through several other methods before being
// passed to executeRequest. The first is deblockRequest, which aligns
// the request with the media's block boundaries; the second is prepare-
// Request which prepares the memory involved in the transfer (involves
// wiring, virtual-to-physical mapping, and breakup of the memory range
// based on the controller's and/or drive's constraints).
//
UInt64 mediaBlockSize = getMediaBlockSize();
// State our assumptions.
assert(buffer->getDirection() == kIODirectionOut);
// If the request is aligned with the media's block boundaries, we can
// short-circuit the deblocker and call prepareRequest directly.
if ( (byteStart % mediaBlockSize) == 0 &&
(buffer->getLength() % mediaBlockSize) == 0 )
{
prepareRequest(byteStart, buffer, completion);
return;
}
deblockRequest(byteStart, buffer, completion);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::addToBytesTransferred(UInt64 bytesTransferred,
UInt64 totalTime, // (ns)
UInt64 latentTime, // (ns)
bool isWrite)
{
//
// Update the total number of bytes transferred, the total transfer time,
// and the total latency time for this drive -- used for statistics.
//
if (isWrite)
{
_statistics[kStatisticsWrites]->addValue(1);
_statistics[kStatisticsBytesWritten]->addValue(bytesTransferred);
_statistics[kStatisticsTotalWriteTime]->addValue(totalTime);
_statistics[kStatisticsLatentWriteTime]->addValue(latentTime);
if (bytesTransferred <= getMediaBlockSize())
_statistics[kStatisticsSingleBlockWrites]->addValue(1);
}
else
{
_statistics[kStatisticsReads]->addValue(1);
_statistics[kStatisticsBytesRead]->addValue(bytesTransferred);
_statistics[kStatisticsTotalReadTime]->addValue(totalTime);
_statistics[kStatisticsLatentReadTime]->addValue(latentTime);
if (bytesTransferred <= getMediaBlockSize())
_statistics[kStatisticsSingleBlockReads]->addValue(1);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::incrementRetries(bool isWrite)
{
//
// Update the total retry count -- used for statistics.
//
if (isWrite)
_statistics[kStatisticsWriteRetries]->addValue(1);
else
_statistics[kStatisticsReadRetries]->addValue(1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::incrementErrors(bool isWrite)
{
//
// Update the total error count -- used for statistics.
//
if (isWrite)
_statistics[kStatisticsWriteErrors]->addValue(1);
else
_statistics[kStatisticsReadErrors]->addValue(1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UInt32 IODrive::getStatistics(UInt64 * statistics,
UInt32 statisticsMaxCount) const
{
//
// Ask the drive to report its operating statistics. The statistics are
// described by the IODrive::Statistics indices. This routine fills the
// caller's buffer, up to the maximum count specified if the real number
// of statistics would overflow the buffer. The return value indicates
// the actual number of statistics copied to the buffer.
//
// If the statistics buffer is not supplied or if the maximum count is
// zero, the routine returns the proposed count of statistics instead.
//
if (statistics == 0)
return kStatisticsCount;
UInt32 statisticsCount = min(kStatisticsCount, statisticsMaxCount);
for (unsigned index = 0; index < statisticsCount; index++)
statistics[index] = _statistics[index]->unsigned64BitValue();
return statisticsCount;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UInt64 IODrive::getStatistic(Statistics statistic) const
{
//
// Ask the drive to report one of its operating statistics.
//
if ((UInt32) statistic >= kStatisticsCount) return 0;
return _statistics[statistic]->unsigned64BitValue();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::prepareRequest(UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageCompletion completion)
{
//
// The prepareRequest method prepares the memory involved in the transfer.
// The memory will be wired down, physically mapped, and broken up based
// on the controller's and drive's constraints.
//
// This method is part of a sequence of methods that are called for each
// read or write request. The first is deblockRequest, which aligns the
// request at the media's block boundaries; the second is prepareRequest,
// which prepares the buffer involved in the transfer; and the third is
// executeRequest, which implements the actual transfer from the drive.
//
executeRequest(byteStart, buffer, completion);
return;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::schedulePoller()
{
//
// Schedule the poller mechanism.
//
// This method assumes that the arbitration lock is held.
//
AbsoluteTime deadline;
retain();
clock_interval_to_deadline(kPollerInterval, kMillisecondScale, &deadline);
thread_call_func_delayed(poller, this, deadline);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::unschedulePoller()
{
//
// Unschedule the poller mechanism.
//
// This method assumes that the arbitration lock is held.
//
if (thread_call_func_cancel(poller, this, true)) release();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::poller(void * target, void *)
{
//
// This method is the timeout handler for the poller mechanism. It polls
// for media and reschedules another timeout if the drive is still closed.
//
IODrive * drive = (IODrive *) target;
drive->pollMedia();
drive->lockForArbitration(); // (disable opens/closes; a recursive lock)
if (!drive->isOpen() && !drive->isInactive())
drive->schedulePoller(); // (schedule the poller, increments retain)
drive->unlockForArbitration(); // (enable opens/closes; a recursive lock)
drive->release(); // (drop the retain associated with this poll)
}
// -----------------------------------------------------------------------------
// Deblocker Implementation
IODrive::DeblockerContext * IODrive::deblockerContextAllocate()
{
//
// Allocate a deblocker context structure. It comes preinitialized with
// a block-sized scratch buffer and an associated memory descriptor, but
// is otherwise uninitialized.
//
// A future implementation may recycle deblocker context structures here
// as an optimization.
//
DeblockerContext * deblockerContext = IONew(DeblockerContext, 1);
UInt64 mediaBlockSize = getMediaBlockSize();
if (deblockerContext)
{
deblockerContext->blockBufferPtr = IONew(UInt8, mediaBlockSize);
if (deblockerContext->blockBufferPtr)
{
deblockerContext->blockBuffer = IOMemoryDescriptor::withAddress(
/* address */ deblockerContext->blockBufferPtr,
/* withLength */ (vm_size_t) mediaBlockSize,
/* withDirection */ kIODirectionNone );
if (deblockerContext->blockBuffer) return deblockerContext;
IODelete(deblockerContext->blockBufferPtr, UInt8, mediaBlockSize);
}
IODelete(deblockerContext, DeblockerContext, 1);
}
return 0; // (failure)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::deblockerContextFree(IODrive::DeblockerContext * deblockerContext)
{
//
// Deallocate a deblocker context structure. The block-sized scratch
// buffer and memory descriptor is automatically deallocated as well.
//
assert(deblockerContext->blockBuffer && deblockerContext->blockBufferPtr);
if (deblockerContext->middleBuffer)
deblockerContext->middleBuffer->release();
deblockerContext->blockBuffer->release();
IODelete(deblockerContext->blockBufferPtr, UInt8, getMediaBlockSize());
IODelete(deblockerContext, DeblockerContext, 1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::deblockRequest(UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageCompletion completion)
{
//
// The deblockRequest method checks to see if the incoming request rests
// on the media's block boundaries, and if not, deblocks it. Deblocking
// involves breaking up the request into sub-requests that rest on block
// boundaries, and performing the appropriate unaligned byte copies from
// the scratch buffer into into the client's request buffer.
//
// This method is part of a sequence of methods that are called for each
// read or write request. The first is deblockRequest, which aligns the
// request at the media's block boundaries; the second is prepareRequest,
// which prepares the memory involved in the transfer; and the third is
// executeRequest, which implements the actual transfer from the drive.
//
// The current implementation of deblockRequest is asynchronous.
//
// A diagram and description of the key players:
// ____ _______________ ______ ______ _______________ ____
// ... | | ... | | ...
// ____|_______________|______ ______|_______________|____
// ^ ^
// |\___/|\_______/|\_____________/|\_____/|
// | | | | | | | | |
// | | | | | | | | |_ byteStart + byteCount
// | | | | | | | |_ _ _ bytesFinal
// | | | | | | |_ _ _ _ _ BLOCK BOUNDARY
// | | | | | |
// | | | | | |_ _ _ _ _ _ _ _ _ bytesMiddle
// | | | | |_ _ _ _ _ _ _ _ _ _ _ _ _ BLOCK BOUNDARY
// | | | |
// | | | |__ _ _ _ _ _ _ _ _ _ _ _ _ _ _ bytesStart
// | | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ byteStart
// | |__ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ offsetStart
// |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ BLOCK BOUNDARY
//
// byteStart = the start of transfer, absolute position on media
// offsetStart = unaligned offset into the first block, zero if aligned
// bytesStart = unaligned bytecount for the first block, zero if aligned
// bytesMiddle = aligned byte count for the middle blocks, zero if none
// bytesFinal = unaligned byte count for the last block, zero if aligned
//
UInt64 byteCount = buffer->getLength();
DeblockerContext * context = deblockerContextAllocate();
bool isWrite = (buffer->getDirection() == kIODirectionOut);
UInt64 mediaBlockSize = getMediaBlockSize();
if (context == 0)
{
complete(completion, kIOReturnNoMemory);
return;
}
//
// This implementation of the deblocker permits only one read-modify-write
// at any given time. Note that other write requests can, and do, proceed
// simultaneously so long as they do not require the deblocker -- refer to
// the read() and the write() routines for the decision logic.
//
if (isWrite) IOTakeLock(_deblockerLockForRMWs);
//
// We split up our calculation logic into two distinct cases:
// (a) all one block transfers with an unaligned end;
// (b) all one block transfers with an aligned end and > 2 block transfers
//
context->offsetStart = byteStart % mediaBlockSize;
if (context->offsetStart + byteCount < mediaBlockSize) // (not '<=')
{
context->bytesStart = byteCount;
context->bytesFinal = 0;
context->bytesMiddle = 0;
}
else // (case b)
{
context->bytesStart = (context->offsetStart) ?
(mediaBlockSize - context->offsetStart) : 0;
context->bytesFinal = (context->offsetStart + byteCount) %
mediaBlockSize;
context->bytesMiddle = byteCount - context->bytesStart -
context->bytesFinal;
}
assert(context->bytesMiddle % mediaBlockSize == 0);
//
// We set the deblock phase to "begin" and pass control to an internal
// completion routine, which implements the deblocking state machine.
//
context->phase = (isWrite) ? kPhaseBeginRMW : kPhaseBegin;
context->bytesTransferred = 0;
context->originalRequest.byteStart = byteStart;
context->originalRequest.buffer = buffer;
context->originalRequest.buffer->retain(); // (retain the original buffer)
context->originalRequest.completion = completion;
if (context->bytesMiddle) // (very bad things)
{
context->middleBuffer = IOMemoryDescriptor::withSubRange(
/* descriptor */ context->originalRequest.buffer,
/* withOffset */ context->bytesStart,
/* withLength */ context->bytesMiddle,
/* withDirection */ context->originalRequest.buffer->getDirection() );
assert(context->middleBuffer);
}
else
{
context->middleBuffer = 0;
}
context->subrequest.buffer = 0;
context->subrequest.byteStart = 0;
context->subrequest.completion.target = this;
context->subrequest.completion.action = deblockerCompletion;
context->subrequest.completion.parameter = context;
deblockerCompletion(this, context, kIOReturnSuccess, 0);
return;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void IODrive::deblockerCompletion(void * target,
void * parameter,
IOReturn status,
UInt64 actualByteCount)
{
//
// This is the completion routine for the aligned deblocker subrequests.
// It performs work on the just-completed phase, if any, transitions to
// the next phase, then builds and issues a transfer for the next phase.
//
//
// For write storage requests, the state machine proceeds as follows:
//
// kPhaseBeginRMW --> { kPhaseStartRM --> kPhaseStartW } -->
// { kPhaseMiddleW } --> { kPhaseFinalRM --> kPhaseFinalW } --> kPhaseDone
//
// For read storage requests, the state machine proceeds as follows:
//
// kPhaseBegin --> { kPhaseStart } --> { kPhaseMiddle } --> { kPhaseFinal }
// --> kPhaseDone
//
// { } denotes skippable states
//
DeblockerContext * context = (DeblockerContext *) parameter;
IODirection direction = kIODirectionIn;
IODrive * drive = (IODrive *) target;
UInt64 mediaBlockSize = drive->getMediaBlockSize();
//
// Complete the work necessary for the just-completed transfer, if any.
// Note that we go out of our way to squeeze every good byte through to
// the disk, even if the transfer failed half way through.
//
// A note for future developers: be mindful that we haven't checked the
// returned actual byte count and status yet.
//
switch (context->phase)
{
case kPhaseStart: // - - - - - - - - - - - - - - - - - - - - - - - - - -
{
UInt64 bytesToCopy;
// Ensure a sufficient amount of the block was read.
if (actualByteCount > context->offsetStart)
{
// Copy the slice of data to be read from the scratch buffer,
// or the subset thereof if the actual transfer came up short.
bytesToCopy = min(context->bytesStart,
actualByteCount - context->offsetStart);
bytesToCopy = context->originalRequest.buffer->writeBytes(
context->bytesTransferred,
context->blockBufferPtr + context->offsetStart,
(UInt32)bytesToCopy);
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += bytesToCopy;
// If the actual transfer came up short (due to writeBytes),
// set an error status.
if (bytesToCopy < context->bytesStart &&
status == kIOReturnSuccess)
{
status = kIOReturnNoSpace; // (writeBytes failure)
}
}
} break;
case kPhaseMiddle: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += actualByteCount;
} break;
case kPhaseFinal: // - - - - - - - - - - - - - - - - - - - - - - - - - -
{
UInt64 bytesToCopy = min(context->bytesFinal, actualByteCount);
// Ensure at least some amount of the block was read.
if (bytesToCopy)
{
// Copy the slice of data to be read from the scratch buffer,
// or the subset thereof if the actual transfer came up short.
bytesToCopy = context->originalRequest.buffer->writeBytes(
context->bytesTransferred,
context->blockBufferPtr,
(UInt32)bytesToCopy);
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += bytesToCopy;
// If the actual transfer came up short (due to writeBytes),
// set an error status.
if (bytesToCopy < context->bytesFinal &&
status == kIOReturnSuccess)
{
status = kIOReturnNoSpace; // (writeBytes failure)
}
}
} break;
case kPhaseStartRM: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Ensure the entire block was read into the scratch buffer.
if (actualByteCount == mediaBlockSize)
{
// Copy the slice of data to be written into the scratch buffer.
if (context->originalRequest.buffer->readBytes(
context->bytesTransferred,
context->blockBufferPtr + context->offsetStart,
(UInt32)context->bytesStart) != (UInt32)context->bytesStart)
{
status = kIOReturnInternalError; // (readBytes failure)
}
}
} break;
case kPhaseStartW: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Ensure the entire block was written and bring total number of
// bytes transferred up to date.
if (actualByteCount == mediaBlockSize)
{
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += context->bytesStart;
}
} break;
case kPhaseMiddleW: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += actualByteCount;
} break;
case kPhaseFinalRM: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Ensure the entire block was read into the scratch buffer.
if (actualByteCount == mediaBlockSize)
{
// Copy the slice of data to be written into the scratch buffer.
if (context->originalRequest.buffer->readBytes(
context->bytesTransferred,
context->blockBufferPtr,
(UInt32)context->bytesFinal) != (UInt32)context->bytesFinal)
{
status = kIOReturnInternalError; // (readBytes failure)
}
}
} break;
case kPhaseFinalW: // - - - - - - - - - - - - - - - - - - - - - - - - -
{
// Ensure the entire block was written and bring total number of
// bytes transferred up to date.
if (actualByteCount == mediaBlockSize)
{
// Bring the total number of bytes transferred up to date.
context->bytesTransferred += context->bytesFinal;
}
} break;
default:
{
// Nothing to do.
} break;
}
//
// Fail the original transfer if an error was detected.
//
if (context->phase != kPhaseBegin && context->phase != kPhaseBeginRMW)
{
assert( status != kIOReturnSuccess ||
actualByteCount == context->subrequest.buffer->getLength() );
if ( status != kIOReturnSuccess ||
actualByteCount != context->subrequest.buffer->getLength() )
{
// Unlock the RMW-lock, to allow the next RMW to proceed.
if (context->originalRequest.buffer->getDirection() ==
kIODirectionOut)
IOUnlock(drive->_deblockerLockForRMWs);
// Release the retain we placed on the request buffer.
context->originalRequest.buffer->release();
// Complete the request.
drive->complete(context->originalRequest.completion,
status,
context->bytesTransferred);
// Release the deblocker context structure.
drive->deblockerContextFree(context);
return;
}
}
//
// Transistion to the next phase of the unaligned transfer.
//
switch (context->phase)
{
case kPhaseBegin:
if (context->bytesStart) { context->phase = kPhaseStart; break; }
case kPhaseStart:
if (context->bytesMiddle) { context->phase = kPhaseMiddle; break; }
case kPhaseMiddle:
if (context->bytesFinal) { context->phase = kPhaseFinal; break; }
case kPhaseFinal:
context->phase = kPhaseDone;
break;
case kPhaseBeginRMW:
if (context->bytesStart) { context->phase = kPhaseStartRM; break; }
case kPhaseStartW:
if (context->bytesMiddle)
{
context->phase = kPhaseMiddleW;
direction = kIODirectionOut;
break;
}
case kPhaseMiddleW:
if (context->bytesFinal) { context->phase = kPhaseFinalRM; break; }
case kPhaseFinalW:
context->phase = kPhaseDone;
break;
case kPhaseStartRM:
context->phase = kPhaseStartW;
direction = kIODirectionOut;
break;
case kPhaseFinalRM:
context->phase = kPhaseFinalW;
direction = kIODirectionOut;
break;
default:
assert(0);
break;
}
//
// Build and execute the transfer for the new phase.
//
switch (context->phase)
{
case kPhaseStart: // - - - - - - - - - - - - - - - - - - - - - - - - - -
case kPhaseStartRM:
case kPhaseStartW:
context->subrequest.byteStart = context->originalRequest.byteStart -
context->offsetStart;
context->subrequest.buffer = context->blockBuffer;
context->subrequest.buffer->initWithAddress(
/* address */ context->blockBufferPtr,
/* withLength */ mediaBlockSize,
/* withDirection */ direction );
break;
case kPhaseMiddle: // - - - - - - - - - - - - - - - - - - - - - - - - -
case kPhaseMiddleW:
context->subrequest.byteStart = context->originalRequest.byteStart +
context->bytesStart;
context->subrequest.buffer = context->middleBuffer;
assert(context->middleBuffer); // (to fix)
break;
case kPhaseFinal: // - - - - - - - - - - - - - - - - - - - - - - - - - -
case kPhaseFinalRM:
case kPhaseFinalW:
context->subrequest.byteStart = context->originalRequest.byteStart +
context->bytesStart +
context->bytesMiddle;
context->subrequest.buffer = context->blockBuffer;
context->subrequest.buffer->initWithAddress(
/* address */ context->blockBufferPtr,
/* withLength */ mediaBlockSize,
/* withDirection */ direction );
break;
case kPhaseDone: // - - - - - - - - - - - - - - - - - - - - - - - - - -
// Unlock the RMW-lock, to allow the next RMW to proceed.
if (context->originalRequest.buffer->getDirection() ==
kIODirectionOut)
IOUnlock(drive->_deblockerLockForRMWs);
// Release the retain we placed on the request buffer.
context->originalRequest.buffer->release();
// Complete the request.
drive->complete(context->originalRequest.completion,
kIOReturnSuccess,
context->bytesTransferred);
// Release the deblocker context structure.
drive->deblockerContextFree(context);
return;
default:
assert(0);
break;
}
// (presuming completion.target/action/parameter are unchanged)
drive->prepareRequest(context->subrequest.byteStart,
context->subrequest.buffer,
context->subrequest.completion);
return;
}