darling-iostoragefamily/IOBlockStorageDriver.cpp
2022-12-03 19:04:00 -08:00

3542 lines
112 KiB
C++

/*
* Copyright (c) 1998-2019 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The 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, QUIET ENJOYMENT 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/IODeviceTreeSupport.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMapper.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOSubMemoryDescriptor.h>
#include <IOKit/pwr_mgt/RootDomain.h>
#include <IOKit/storage/IOBlockStorageDevice.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOBlockStoragePerfControlExports.h>
#include <kern/energy_perf.h>
#include <kern/thread_call.h>
#define super IOStorage
OSDefineMetaClassAndStructors(IOBlockStorageDriver, IOStorage)
static char * strclean(char * s)
{
//
// strclean() trims any spaces at either end of the string, strips any
// control characters within the string, and collapses any sequence of
// spaces within the string into a single space.
//
int sourceIndex = 0, targetIndex = 0, targetLength = 0;
for ( ; s[sourceIndex] > '\0' && s[sourceIndex] <= ' '; sourceIndex++ );
for ( ; s[sourceIndex]; sourceIndex++ )
{
if ( s[sourceIndex] < '\0' || s[sourceIndex] >= ' ' )
{
if ( s[sourceIndex] != ' ' )
{
if ( targetLength < targetIndex )
{
targetIndex = targetLength + 1;
}
targetLength = targetIndex + 1;
}
s[targetIndex++] = s[sourceIndex];
}
}
s[targetLength] = '\0';
return s;
}
IOBlockStorageDevice * IOBlockStorageDriver::getProvider() const
{
//
// Obtain this object's provider. We override the superclass's method to
// return a more specific subclass of IOService -- IOBlockStorageDevice.
// This method serves simply as a convenience to subclass developers.
//
return (IOBlockStorageDevice *) IOService::getProvider();
}
bool IOBlockStorageDriver::init(OSDictionary * properties)
{
//
// Initialize this object's minimal state.
//
// Ask our superclass' opinion.
if (super::init(properties) == false) return false;
// Initialize our state.
_expansionData = IONew(ExpansionData, 1);
if (_expansionData == 0) return false;
initMediaState();
_ejectable = false;
_removable = false;
_mediaBlockSize = 0;
_maxBlockNumber = 0;
_writeProtected = false;
_maxReadBlockTransfer = 0;
_maxWriteBlockTransfer = 0;
_maxReadByteTransfer = 0;
_maxWriteByteTransfer = 0;
_maxReadSegmentTransfer = 0;
_maxWriteSegmentTransfer = 0;
_maxReadSegmentByteTransfer = 0;
_maxWriteSegmentByteTransfer = 0;
_minSegmentAlignmentByteTransfer = 4;
_maxSegmentWidthByteTransfer = 0;
_contexts = NULL;
_contextsLock = IOSimpleLockAlloc();
_contextsCount = 0;
_contextsMaxCount = 32;
_perfControlClient = NULL;
if (_contextsLock == 0)
return false;
_deblockRequestWriteLock = IOLockAlloc();
_deblockRequestWriteLockCount = 0;
_openAssertions = 0;
_openClients = OSSet::withCapacity(2);
_powerEventNotifier = 0;
for (unsigned index = 0; index < kStatisticsCount; index++)
_statistics[index] = OSNumber::withNumber(0ULL, 64);
if (_deblockRequestWriteLock == 0 || _openClients == 0)
return false;
for (unsigned index = 0; index < kStatisticsCount; index++)
if (_statistics[index] == 0) return false;
// Create our registry properties.
OSDictionary * statistics = OSDictionary::withCapacity(kStatisticsCount);
if (statistics == 0) return false;
statistics->setObject( kIOBlockStorageDriverStatisticsBytesReadKey,
_statistics[kStatisticsBytesRead] );
statistics->setObject( kIOBlockStorageDriverStatisticsBytesWrittenKey,
_statistics[kStatisticsBytesWritten] );
statistics->setObject( kIOBlockStorageDriverStatisticsReadErrorsKey,
_statistics[kStatisticsReadErrors] );
statistics->setObject( kIOBlockStorageDriverStatisticsWriteErrorsKey,
_statistics[kStatisticsWriteErrors] );
statistics->setObject( kIOBlockStorageDriverStatisticsLatentReadTimeKey,
_statistics[kStatisticsLatentReadTime] );
statistics->setObject( kIOBlockStorageDriverStatisticsLatentWriteTimeKey,
_statistics[kStatisticsLatentWriteTime] );
statistics->setObject( kIOBlockStorageDriverStatisticsReadsKey,
_statistics[kStatisticsReads] );
statistics->setObject( kIOBlockStorageDriverStatisticsWritesKey,
_statistics[kStatisticsWrites] );
statistics->setObject( kIOBlockStorageDriverStatisticsReadRetriesKey,
_statistics[kStatisticsReadRetries] );
statistics->setObject( kIOBlockStorageDriverStatisticsWriteRetriesKey,
_statistics[kStatisticsWriteRetries] );
statistics->setObject( kIOBlockStorageDriverStatisticsTotalReadTimeKey,
_statistics[kStatisticsTotalReadTime] );
statistics->setObject( kIOBlockStorageDriverStatisticsTotalWriteTimeKey,
_statistics[kStatisticsTotalWriteTime] );
setProperty(kIOBlockStorageDriverStatisticsKey, statistics);
statistics->release();
return true;
}
bool IOBlockStorageDriver::start(IOService * provider)
{
//
// This method is called once we have been attached to the provider object.
//
bool success;
// Open the block storage device.
success = open(this);
if (success)
{
// Prepare the block storage driver for operation.
success = handleStart(provider);
// Close the block storage device.
close(this);
}
if (success)
{
// 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 success;
}
bool IOBlockStorageDriver::didTerminate(IOService * provider,
IOOptionBits options,
bool * defer)
{
// Try to teardown.
decommissionMedia(false);
return super::didTerminate(provider, options, defer);
}
#if TARGET_OS_OSX
bool IOBlockStorageDriver::yield(IOService * provider,
IOOptionBits options,
void * argument)
{
return false;
}
#endif /* TARGET_OS_OSX */
void IOBlockStorageDriver::free()
{
//
// Free all of this object's outstanding resources.
//
while (_contexts)
{
Context * context = _contexts;
_contexts = context->next;
_contextsCount--;
if (context->perfControlContext)
context->perfControlContext->release();
IODelete(context, Context, 1);
}
if (_contextsLock) IOSimpleLockFree(_contextsLock);
if (_deblockRequestWriteLock) IOLockFree(_deblockRequestWriteLock);
if (_openClients) _openClients->release();
for (unsigned index = 0; index < kStatisticsCount; index++)
if (_statistics[index]) _statistics[index]->release();
if (_perfControlClient) {
_perfControlClient->unregisterDevice(this, this);
_perfControlClient->release();
_perfControlClient = NULL;
}
if (_expansionData) IODelete(_expansionData, ExpansionData, 1);
super::free();
}
bool IOBlockStorageDriver::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.
//
// This method assumes that the arbitration lock is held.
//
assert(client);
// Ensure there is media in the block storage device.
if (client != this)
{
if (_mediaObject == NULL)
{
return false;
}
}
// Handle the first open in a special case.
if (_openClients->getCount() == 0)
{
// Open the block storage device.
if (getProvider()->open(this) == false)
{
return false;
}
}
// Process the open.
if (client != this)
{
_openClients->setObject(client);
}
else
{
if (_openAssertions == 0)
{
_openClients->setObject(client);
}
_openAssertions++;
}
return true;
}
bool IOBlockStorageDriver::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.
//
// This method assumes that the arbitration lock is held.
//
if (client)
{
return _openClients->containsObject(client);
}
else
{
return _openClients->getCount() ? true : false;
}
}
void IOBlockStorageDriver::handleClose(IOService * client, IOOptionBits options)
{
//
// The handleClose method drops the incoming client's access to this object.
//
// This method assumes that the arbitration lock is held.
//
assert(client);
// Process the close.
if (client != this)
{
_openClients->removeObject(client);
}
else
{
_openAssertions--;
if (_openAssertions == 0)
{
_openClients->removeObject(client);
}
}
// Handle the last close in a special case.
if (_openClients->getCount() == 0)
{
if (_mediaObject)
{
if (_mediaObject->isInactive())
{
message(kIOMessageServiceIsRequestingClose, getProvider(), 0);
}
}
// Close the block storage device.
getProvider()->close(this);
}
}
void IOBlockStorageDriver::read(IOService * client,
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion)
{
//
// The read method is the receiving end for all read requests from the
// storage framework, ie. via the media object created by this driver.
//
// This method initiates a sequence of methods (stages) for each read/write
// request. The first is prepareRequest, which allocates and prepares some
// context for the transfer; the second is deblockRequest, which aligns the
// transfer at the media's block boundaries; third is breakUpRequest, which
// breaks up the transfer into multiple sub-transfers when certain hardware
// constraints are exceeded; fourth is executeRequest, which implements the
// actual transfer from the block storage device.
//
// State our assumptions.
assert( buffer->getDirection( ) == kIODirectionIn );
// Prepare the transfer.
prepareRequest( byteStart, buffer, attributes, completion );
}
void IOBlockStorageDriver::write(IOService * client,
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion)
{
//
// The write method is the receiving end for all write requests from the
// storage framework, ie. via the media object created by this driver.
//
// This method initiates a sequence of methods (stages) for each read/write
// request. The first is prepareRequest, which allocates and prepares some
// context for the transfer; the second is deblockRequest, which aligns the
// transfer at the media's block boundaries; third is breakUpRequest, which
// breaks up the transfer into multiple sub-transfers when certain hardware
// constraints are exceeded; fourth is executeRequest, which implements the
// actual transfer from the block storage device.
//
// State our assumptions.
assert( buffer->getDirection( ) == kIODirectionOut );
// Prepare the transfer.
prepareRequest( byteStart, buffer, attributes, completion );
}
void IOBlockStorageDriver::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 -- used for statistics.
//
if (isWrite)
{
_statistics[kStatisticsWrites]->addValue(1);
_statistics[kStatisticsBytesWritten]->addValue(bytesTransferred);
_statistics[kStatisticsTotalWriteTime]->addValue(totalTime);
_statistics[kStatisticsLatentWriteTime]->addValue(latentTime);
}
else
{
_statistics[kStatisticsReads]->addValue(1);
_statistics[kStatisticsBytesRead]->addValue(bytesTransferred);
_statistics[kStatisticsTotalReadTime]->addValue(totalTime);
_statistics[kStatisticsLatentReadTime]->addValue(latentTime);
}
}
void IOBlockStorageDriver::incrementRetries(bool isWrite)
{
//
// Update the total retry count -- used for statistics.
//
if (isWrite)
_statistics[kStatisticsWriteRetries]->addValue(1);
else
_statistics[kStatisticsReadRetries]->addValue(1);
}
void IOBlockStorageDriver::incrementErrors(bool isWrite)
{
//
// Update the total error count -- used for statistics.
//
if (isWrite)
_statistics[kStatisticsWriteErrors]->addValue(1);
else
_statistics[kStatisticsReadErrors]->addValue(1);
}
UInt32 IOBlockStorageDriver::getStatistics(UInt64 * statistics,
UInt32 statisticsMaxCount) const
{
//
// Ask the driver to report its operating statistics.
//
// The statistics are each indexed by IOBlockStorageDriver::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 IOBlockStorageDriver::getStatistic(Statistics statistic) const
{
//
// Ask the driver to report one of its operating statistics.
//
if ((UInt32) statistic >= kStatisticsCount) return 0;
return _statistics[statistic]->unsigned64BitValue();
}
IOBlockStorageDriver::Context * IOBlockStorageDriver::allocateContext()
{
//
// Allocate a context structure for a read/write operation.
//
Context * context;
IOSimpleLockLock(_contextsLock);
context = _contexts;
if (context)
{
_contexts = context->next;
_contextsCount--;
}
IOSimpleLockUnlock(_contextsLock);
if (context == 0)
{
context = IONew(Context, 1);
if (context)
{
bzero(context, sizeof(Context));
if (_perfControlClient) {
context->perfControlContext = _perfControlClient->copyWorkContext();
}
}
}
else
{
auto perfControlContext = context->perfControlContext;
bzero(context, sizeof(Context));
if (perfControlContext) {
context->perfControlContext = perfControlContext;
}
}
return context;
}
void IOBlockStorageDriver::deleteContext(
IOBlockStorageDriver::Context * context)
{
//
// Delete a context structure from a read/write operation.
//
IOSimpleLockLock(_contextsLock);
if (_contextsCount < _contextsMaxCount)
{
context->next = _contexts;
_contexts = context;
_contextsCount++;
context = 0;
}
IOSimpleLockUnlock(_contextsLock);
if (context)
{
if (context->perfControlContext)
context->perfControlContext->release();
IODelete(context, Context, 1);
}
}
void IOBlockStorageDriver::prepareRequestCompletion(void * target,
void * parameter,
IOReturn status,
UInt64 actualByteCount)
{
//
// This is the completion routine for the prepared request. It updates
// the driver's statistics, performs some clean up work, then calls the
// original request's completion routine.
//
Context * context = (Context *) parameter;
IOBlockStorageDriver * driver = (IOBlockStorageDriver *) target;
bool isWrite;
AbsoluteTime time;
UInt64 timeInNanoseconds;
isWrite = (context->request.buffer->getDirection() == kIODirectionOut);
// Update the error state, on a short transfer.
if (actualByteCount < context->request.buffer->getLength())
{
if (status == kIOReturnSuccess)
{
status = kIOReturnUnderrun;
}
}
// Update the total number of bytes transferred and the total transfer time.
clock_get_uptime(&time);
auto perfControlClient = driver->_perfControlClient;
if (perfControlClient && context->perfControlContext) {
IOPerfControlClient::WorkEndArgs end_args;
end_args.end_time = time;
perfControlClient->workEndWithContext(driver, context->perfControlContext, &end_args);
}
SUB_ABSOLUTETIME(&time, &context->timeStart);
absolutetime_to_nanoseconds(time, &timeInNanoseconds);
driver->addToBytesTransferred(actualByteCount, timeInNanoseconds, 0, isWrite);
// Update the total error count.
if (status != kIOReturnSuccess)
{
driver->incrementErrors(isWrite);
}
// Complete the transfer request.
IOStorage::complete(&context->request.completion, status, actualByteCount);
// Release our resources.
context->request.buffer->release();
driver->deleteContext(context);
driver->release();
}
#if TARGET_OS_OSX
void IOBlockStorageDriver::schedulePoller()
{
}
void IOBlockStorageDriver::unschedulePoller()
{
}
#endif /* TARGET_OS_OSX */
IOReturn IOBlockStorageDriver::message(UInt32 type,
IOService * provider,
void * argument)
{
//
// Generic entry point for calls from the provider. A return value of
// kIOReturnSuccess indicates that the message was received, and where
// applicable, that it was successful.
//
switch (type)
{
case kIOMessageMediaParametersHaveChanged:
{
IOReturn status;
if (open(this)) {
status = recordMediaParameters();
if (status == kIOReturnSuccess) {
UInt64 nbytes;
IOMedia *m;
if (_maxBlockNumber) {
nbytes = _mediaBlockSize * (_maxBlockNumber + 1);
} else {
nbytes = 0;
}
m = instantiateMediaObject(0,nbytes,(UInt32) _mediaBlockSize,NULL);
if (m) {
lockForArbitration();
if (_mediaObject) {
_mediaObject->init( m->getBase(),
m->getSize(),
m->getPreferredBlockSize(),
m->getAttributes(),
m->isWhole(),
m->isWritable(),
m->getContentHint() );
_mediaObject->messageClients(kIOMessageServicePropertyChange);
} else {
status = kIOReturnNoMedia;
}
unlockForArbitration();
m->release();
} else {
status = kIOReturnBadArgument;
}
}
close(this);
} else {
status = kIOReturnNotAttached;
}
return status;
}
case kIOMessageMediaStateHasChanged:
{
IOReturn status;
if (open(this)) {
status = mediaStateHasChanged((IOMediaState) (uintptr_t) argument);
close(this);
} else {
status = kIOReturnNotAttached;
}
return status;
}
case kIOMessageServiceIsRequestingClose:
{
IOReturn status;
status = decommissionMedia(false);
return status;
}
default:
{
return super::message(type, provider, argument);
}
}
}
/* Accept a new piece of media, doing whatever's necessary to make it
* show up properly to the system. The arbitration lock is assumed to
* be held during the call.
*/
IOReturn
IOBlockStorageDriver::acceptNewMedia(void)
{
IOReturn result;
bool ok;
UInt64 nbytes;
char name[128];
bool nameSep;
IOMedia *m;
if (_maxBlockNumber) {
nbytes = _mediaBlockSize * (_maxBlockNumber + 1);
} else {
nbytes = 0;
}
/* Instantiate a media object and attach it to ourselves. */
name[0] = 0;
nameSep = false;
if (getProvider()->getVendorString()) {
strlcat(name, getProvider()->getVendorString(), sizeof(name) - strlen(name));
nameSep = true;
}
if (getProvider()->getProductString()) {
if (nameSep == true) strlcat(name, " ", sizeof(name) - strlen(name));
strlcat(name, getProvider()->getProductString(), sizeof(name) - strlen(name));
nameSep = true;
}
if (nameSep == true) strlcat(name, " ", sizeof(name) - strlen(name));
strlcat(name, "Media", sizeof(name) - strlen(name));
strclean(name);
m = instantiateMediaObject(0,nbytes,(UInt32) _mediaBlockSize,name);
result = (m) ? kIOReturnSuccess : kIOReturnBadArgument;
if (result == kIOReturnSuccess) {
if (getProperty(kIOMediaIconKey, gIOServicePlane)) {
m->removeProperty(kIOMediaIconKey);
}
ok = m->attach(this); /* attach media object above us */
if (ok) {
IOService *parent = this;
OSNumber *unit = NULL;
OSNumber *unitLUN = NULL;
OSString *unitName = NULL;
/* Wire the media object to the device tree. */
while ((parent = parent->getProvider())) {
if (!unit) {
unit = OSDynamicCast(OSNumber, parent->getProperty("IOUnit"));
}
if (!unitLUN) {
unitLUN = OSDynamicCast(OSNumber, parent->getProperty("IOUnitLUN"));
}
if (!unitName) {
unitName = OSDynamicCast(OSString, parent->getProperty("IOUnitName"));
}
if (parent->inPlane(gIODTPlane)) {
IORegistryEntry *child;
IORegistryIterator *children;
if (!unit || !parent->getProvider()) {
break;
}
children = IORegistryIterator::iterateOver(parent, gIODTPlane);
if (!children) {
break;
}
while ((child = children->getNextObject())) {
if (!OSDynamicCast(IOMedia, child)) {
child->detachAll(gIODTPlane);
}
}
children->release();
if (m->attachToParent(parent, gIODTPlane)) {
char location[ 32 ];
if (unitLUN && unitLUN->unsigned32BitValue()) {
snprintf(location, sizeof(location), "%x,%x:0", unit->unsigned32BitValue(), unitLUN->unsigned32BitValue());
} else {
snprintf(location, sizeof(location), "%x:0", unit->unsigned32BitValue());
}
m->setLocation(location, gIODTPlane);
m->setName(unitName ? unitName->getCStringNoCopy() : "", gIODTPlane);
}
break;
}
}
lockForArbitration();
_mediaObject = m;
_mediaObject->retain();
unlockForArbitration();
m->registerService(kIOServiceAsynchronous); /* enable matching */
} else {
result = kIOReturnNoMemory; /* give up now */
}
m->release();
}
return(result);
}
IOReturn
IOBlockStorageDriver::checkForMedia(void)
{
IOReturn result;
bool currentState;
bool changed;
result = getProvider()->reportMediaState(&currentState,&changed);
if (result != kIOReturnSuccess) { /* the poll operation failed */
IOLog("%s[IOBlockStorageDriver]::checkForMedia; err '%s' from reportMediaState\n",
getName(),stringFromReturn(result));
} else {
changed = _mediaObject ? !currentState : currentState;
if (changed) { /* the poll succeeded, media state has changed */
result = mediaStateHasChanged(currentState ? kIOMediaStateOnline
: kIOMediaStateOffline);
}
}
return(result);
}
IOReturn
IOBlockStorageDriver::mediaStateHasChanged(IOMediaState state)
{
IOReturn result;
/* The media has changed state. See if it's just inserted or removed. */
if (state == kIOMediaStateOnline) { /* media is now present */
if (_mediaObject) {
return(kIOReturnBadArgument);
}
initMediaState(); /* clear all knowledge of the media */
/* Allow a subclass to decide whether we accept the media. Such a
* decision might be based on things like password-protection, etc.
*/
if (validateNewMedia() == false) { /* we're told to reject it */
rejectMedia(); /* so let subclass do whatever it wants */
return(kIOReturnSuccess); /* pretend nothing happened */
}
result = recordMediaParameters(); /* learn about media */
if (result != kIOReturnSuccess) { /* couldn't record params */
IOLog("%s[IOBlockStorageDriver]::mediaStateHasChanged: err '%s' from recordMediaParameters\n",
getName(),stringFromReturn(result));
return(result);
}
/* Now we do what's necessary to make the new media
* show up properly in the system.
*/
result = acceptNewMedia();
if (result != kIOReturnSuccess) {
IOLog("%s[IOBlockStorageDriver]::mediaStateHasChanged; err '%s' from acceptNewMedia\n",
getName(),stringFromReturn(result));
}
return(result); /* all done, new media is ready */
} else { /* media is now absent */
result = decommissionMedia(true); /* force a teardown */
if (result != kIOReturnSuccess && result != kIOReturnNoMedia) {
IOLog("%s[IOBlockStorageDriver]::mediaStateHasChanged; err '%s' from decommissionNewMedia\n",
getName(),stringFromReturn(result));
return(result);
}
return(kIOReturnSuccess); /* all done; media is gone */
}
}
#if TARGET_OS_OSX
UInt64
IOBlockStorageDriver::constrainByteCount(UInt64 /* requestedCount */ ,bool isWrite)
{
if (isWrite) {
return(_maxWriteByteTransfer);
} else {
return(_maxReadByteTransfer);
}
}
#endif /* TARGET_OS_OSX */
/* Decommission a piece of media that has become unavailable either due to
* ejection or some outside force (e.g. the Giant Hand of the User).
* (I prefer the term "decommission" rather than "abandon." The former implies
* a well-determined procedure, whereas the latter implies leaving the media
* in an orphaned state.)
*/
/* Tear down the stack above the specified object. Usually these objects will
* be of type IOMedia, but they could be any IOService.
*/
IOReturn
IOBlockStorageDriver::decommissionMedia(bool forcible)
{
IOMedia *m = NULL;
IOReturn result;
lockForArbitration();
if (_mediaObject) {
/* If this is a forcible decommission (i.e. media is gone), we
* forget about the media.
*/
if (forcible || !_openClients->containsObject(_mediaObject)) {
m = _mediaObject;
_mediaObject = 0;
result = kIOReturnSuccess;
} else {
result = kIOReturnBusy;
}
} else {
result = kIOReturnNoMedia;
}
unlockForArbitration();
if (m) {
IORegistryEntry * parent;
/* Unwire the media object from the device tree. */
if ( (parent = m->getParentEntry(gIODTPlane)) ) {
m->detachFromParent(parent, gIODTPlane);
}
if (!m->isInactive()) {
m->terminate();
}
m->release();
}
return(result);
}
IOReturn
IOBlockStorageDriver::ejectMedia(void)
{
IOReturn result;
if (_ejectable)
{
result = decommissionMedia(false); /* try to teardown */
}
else
{
lockForArbitration();
if (_mediaObject) {
if (!_openClients->containsObject(_mediaObject)) {
result = kIOReturnSuccess;
} else {
result = kIOReturnBusy;
}
} else {
result = kIOReturnNoMedia;
}
unlockForArbitration();
}
if (result == kIOReturnSuccess) { /* eject */
if (!_writeProtected) {
(void)getProvider()->doSynchronize(0,0);
}
result = getProvider()->doEjectMedia();
}
return(result);
}
void
IOBlockStorageDriver::executeRequest(UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion,
IOBlockStorageDriver::Context * context)
{
UInt32 flags = 0;
UInt64 block;
UInt64 nblks;
IOReturn result;
if (!_mediaObject) { /* no media? you lose */
complete(completion,kIOReturnNoMedia,0);
return;
}
/* We know that we are never called with a request too large,
* nor one that is misaligned with a block.
*/
assert((byteStart % context->block.size) == 0);
assert((buffer->getLength() % context->block.size) == 0);
block = byteStart / context->block.size;
nblks = buffer->getLength() / context->block.size;
/* Now the protocol-specific provider implements the actual
* start of the data transfer: */
if (attributes) {
if (attributes->priority > kIOStoragePriorityDefault) {
flags |= IO_PRIORITY_LOW;
}
}
if (_solidState) {
flags |= IO_MEDIUM_SOLID_STATE;
}
if (buffer->getDirection() == kIODirectionIn) {
io_rate_update(flags, 1, 0, buffer->getLength(), 0);
} else {
io_rate_update(flags, 0, 1, 0, buffer->getLength());
}
// This is where we adjust the offset for this access to the nand layer.
// We already maintain this buffer's file offset in attributes.fileOffset
if (!attributes) {
attributes = &context->request.attributes;
}
attributes->adjustedOffset = ((SInt64)byteStart - (SInt64)context->request.byteStart);
result = getProvider()->doAsyncReadWrite(buffer,block,nblks,attributes,completion);
if (result != kIOReturnSuccess) { /* it failed to start */
if (result != kIOReturnNotPermitted) { /* expected error from content protection */
IOLog("%s[IOBlockStorageDriver]; executeRequest: request failed to start!\n",getName());
}
complete(completion,result);
return;
}
}
IOReturn
IOBlockStorageDriver::formatMedia(UInt64 byteCapacity)
{
IOReturn result;
result = decommissionMedia(false); /* try to teardown */
if (result == kIOReturnSuccess) { /* format */
result = getProvider()->doFormatMedia(byteCapacity);
if (result == kIOReturnSuccess) {
result = mediaStateHasChanged(kIOMediaStateOnline);
} else {
(void)mediaStateHasChanged(kIOMediaStateOnline);
}
}
return(result);
}
const char *
IOBlockStorageDriver::getDeviceTypeName(void)
{
return(kIOBlockStorageDeviceTypeGeneric);
}
UInt32
IOBlockStorageDriver::getFormatCapacities(UInt64 * capacities,
UInt32 capacitiesMaxCount) const
{
return(getProvider()->doGetFormatCapacities(capacities,capacitiesMaxCount));
}
UInt64
IOBlockStorageDriver::getMediaBlockSize() const
{
return(_mediaBlockSize);
}
IOMediaState
IOBlockStorageDriver::getMediaState() const
{
if (_mediaObject) {
return(kIOMediaStateOnline);
} else {
return(kIOMediaStateOffline);
}
}
IOReturn
IOBlockStorageDriver::handlePowerEvent(void *target,void *refCon,
UInt32 messageType,IOService *provider,
void *messageArgument,vm_size_t argSize)
{
IOBlockStorageDriver *driver = (IOBlockStorageDriver *)target;
IOReturn result;
switch (messageType) {
case kIOMessageSystemWillPowerOff:
case kIOMessageSystemWillRestart:
if (driver->open(driver)) {
if (driver->_mediaObject) {
if (!driver->_writeProtected) {
driver->getProvider()->doSynchronize(0,0);
}
if (!driver->_removable && (messageType == kIOMessageSystemWillPowerOff)) {
driver->getProvider()->doEjectMedia();
}
}
driver->close(driver);
}
result = kIOReturnSuccess;
break;
default:
result = kIOReturnUnsupported;
break;
}
return(result);
}
bool
IOBlockStorageDriver::handleStart(IOService * provider)
{
OSObject *object;
OSNumber *number;
IOReturn result;
/* The protocol-specific provider determines whether the media is removable. */
result = getProvider()->reportRemovability(&_removable);
if (result != kIOReturnSuccess) {
IOLog("%s[IOBlockStorageDriver]::handleStart; err '%s' from reportRemovability\n",
getName(),stringFromReturn(result));
return(false);
}
if (_removable) {
/* The protocol-specific provider determines whether the media is ejectable
* under software control.
*/
result = getProvider()->reportEjectability(&_ejectable);
if (result != kIOReturnSuccess) {
IOLog("%s[IOBlockStorageDriver]::handleStart; err '%s' from reportEjectability\n",
getName(),stringFromReturn(result));
return(false);
}
} else { /* fixed disk: not ejectable */
OSDictionary *dictionary = OSDynamicCast(OSDictionary, getProvider()->getProperty(kIOPropertyDeviceCharacteristicsKey));
if (dictionary) {
OSString *string = OSDynamicCast(OSString, dictionary->getObject(kIOPropertyMediumTypeKey));
if (string) {
if (string->isEqualTo(kIOPropertyMediumTypeSolidStateKey)) {
_solidState = true;
}
}
}
_ejectable = false;
}
/* Obtain the constraint values for reads and writes. */
object = copyProperty(kIOMaximumBlockCountReadKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxReadBlockTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumBlockCountWriteKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxWriteBlockTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumByteCountReadKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxReadByteTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumByteCountWriteKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxWriteByteTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumSegmentCountReadKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxReadSegmentTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumSegmentCountWriteKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxWriteSegmentTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumSegmentByteCountReadKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxReadSegmentByteTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMaximumSegmentByteCountWriteKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_maxWriteSegmentByteTransfer = number->unsigned64BitValue();
}
object->release();
}
object = copyProperty(kIOMinimumSegmentAlignmentByteCountKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_minSegmentAlignmentByteTransfer = number->unsigned64BitValue();
}
object->release();
} else {
getProvider()->setProperty(kIOMinimumSegmentAlignmentByteCountKey, _minSegmentAlignmentByteTransfer, 64);
}
object = copyProperty(kIOMaximumSegmentAddressableBitCountKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
if (number->unsigned64BitValue()) {
if (number->unsigned64BitValue() < 64) {
_maxSegmentWidthByteTransfer = 1ULL << number->unsigned64BitValue();
}
}
}
object->release();
}
object = copyProperty(kIOCommandPoolSizeKey, gIOServicePlane);
if (object) {
number = OSDynamicCast(OSNumber, object);
if (number) {
_contextsMaxCount = number->unsigned32BitValue();
}
object->release();
}
/* Set up perfcontrol client to track IO */
_perfControlClient = IOPerfControlClient::copyClient(this, 0);
if (_perfControlClient ) {
IOReturn ret = _perfControlClient->registerDevice(this, this);
if ( ret != kIOReturnSuccess ) {
_perfControlClient->release();
_perfControlClient = NULL;
}
}
/* Check for the device being ready with media inserted:
* This will likely initiate IO if the device is found.
*/
result = checkForMedia();
/* The poll should never fail for nonremovable media: */
if (result != kIOReturnSuccess && !_removable) {
IOLog("%s[IOBlockStorageDriver]::handleStart: err '%s' from checkForMedia\n",
getName(),stringFromReturn(result));
return(false);
}
/* Set up the power event handler for restarts and shutdowns: */
_powerEventNotifier = registerPrioritySleepWakeInterest(handlePowerEvent,this);
if (_powerEventNotifier) {
retain();
}
return(true);
}
#if TARGET_OS_OSX
bool
IOBlockStorageDriver::handleYield(IOService * provider,
IOOptionBits options,
void * argument)
{
return false;
}
#endif /* TARGET_OS_OSX */
void
IOBlockStorageDriver::initMediaState(void)
{
_mediaType = 0;
}
IOMedia *
IOBlockStorageDriver::instantiateDesiredMediaObject(void)
{
return(new IOMedia);
}
IOMedia *
IOBlockStorageDriver::instantiateMediaObject(UInt64 base,UInt64 byteSize,
UInt32 blockSize,char *mediaName)
{
IOMediaAttributeMask attributes = 0;
IOMedia *m;
bool result;
m = instantiateDesiredMediaObject();
if (m == NULL) {
return(NULL);
}
attributes |= _ejectable ? kIOMediaAttributeEjectableMask : 0;
attributes |= _removable ? kIOMediaAttributeRemovableMask : 0;
result = m->init( base, /* base byte offset */
byteSize, /* byte size */
blockSize, /* preferred block size */
attributes, /* attributes */
true, /* TRUE if whole physical media */
!_writeProtected, /* TRUE if writable */
""); /* content hint */
if (result) {
const char *picture = "External.icns";
if (_removable) {
picture = "Removable.icns";
} else {
OSDictionary *dictionary = OSDynamicCast(OSDictionary, getProvider()->getProperty(kIOPropertyProtocolCharacteristicsKey));
if (dictionary) {
OSString *string = OSDynamicCast(OSString, dictionary->getObject(kIOPropertyPhysicalInterconnectLocationKey));
if (string) {
if (string->isEqualTo(kIOPropertyExternalKey)) {
picture = "External.icns";
} else {
picture = "Internal.icns";
}
}
}
}
if (picture) {
OSDictionary *dictionary = OSDictionary::withCapacity(2);
OSString *identifier = OSString::withCString("com.apple.iokit.IOStorageFamily");
OSString *resourceFile = OSString::withCString(picture);
if (dictionary && identifier && resourceFile) {
dictionary->setObject("CFBundleIdentifier", identifier);
dictionary->setObject("IOBundleResourceFile", resourceFile);
}
m->setProperty(kIOMediaIconKey, dictionary);
if (resourceFile) {
resourceFile->release();
}
if (identifier) {
identifier->release();
}
if (dictionary) {
dictionary->release();
}
}
if (mediaName) {
m->setName(mediaName);
}
return(m);
} else { /* some init error */
m->release();
return(NULL); /* beats me...call it this error */
}
}
bool
IOBlockStorageDriver::isMediaEjectable(void) const
{
return(_ejectable);
}
bool
IOBlockStorageDriver::isMediaRemovable(void) const
{
return(_removable);
}
#if TARGET_OS_OSX
bool
IOBlockStorageDriver::isMediaPollExpensive(void) const
{
return(false);
}
bool
IOBlockStorageDriver::isMediaPollRequired(void) const
{
return(false);
}
#endif /* TARGET_OS_OSX */
bool
IOBlockStorageDriver::isMediaWritable(void) const
{
return(!_writeProtected);
}
#if TARGET_OS_OSX
IOReturn
IOBlockStorageDriver::lockMedia(bool locked)
{
return(kIOReturnUnsupported);
}
IOReturn
IOBlockStorageDriver::pollMedia(void)
{
return(kIOReturnUnsupported);
}
#endif /* TARGET_OS_OSX */
IOReturn
IOBlockStorageDriver::recordMediaParameters(void)
{
IOReturn result;
/* Determine the device's block size and max block number.
* What should an unformatted device report? All zeroes.
*/
result = getProvider()->reportBlockSize(&_mediaBlockSize);
if (result != kIOReturnSuccess) {
goto err;
}
result = getProvider()->reportMaxValidBlock(&_maxBlockNumber);
if (result != kIOReturnSuccess) {
goto err;
}
/* Is the media write-protected? */
result = getProvider()->reportWriteProtection(&_writeProtected);
if (result != kIOReturnSuccess) {
goto err;
}
return(kIOReturnSuccess); /* everything was successful */
/* If we fall thru to here, we had some kind of error. Set everything to
* a reasonable state since we haven't got any real information.
*/
err:
return(result);
}
void
IOBlockStorageDriver::rejectMedia(void)
{
(void)getProvider()->doEjectMedia(); /* eject it, ignoring any error */
}
void
IOBlockStorageDriver::stop(IOService * provider)
{
if (_powerEventNotifier) {
_powerEventNotifier->remove();
_powerEventNotifier = NULL;
release();
}
if (_perfControlClient) {
_perfControlClient->unregisterDevice(this, this);
_perfControlClient->release();
_perfControlClient = NULL;
}
super::stop(provider);
}
IOReturn
IOBlockStorageDriver::synchronize(IOService * client,
UInt64 byteStart,
UInt64 byteCount,
IOStorageSynchronizeOptions options)
{
UInt64 blockStart;
UInt64 blockCount;
#if TARGET_OS_OSX
if ( _respondsTo_synchronizeCache )
{
if ( options == _kIOStorageSynchronizeOption_super__synchronizeCache )
{
options = 0;
}
else
{
return IOStorage::synchronize( client, byteStart, byteCount, options );
}
}
#endif /* TARGET_OS_OSX */
if ( ( options & kIOStorageSynchronizeOptionReserved ) )
{
return kIOReturnBadArgument;
}
blockStart = byteStart / _mediaBlockSize;
if ( byteCount )
{
blockCount = ( byteStart + byteCount + _mediaBlockSize - 1 ) / _mediaBlockSize - blockStart;
}
else
{
blockCount = 0;
}
return getProvider( )->doSynchronize( blockStart, blockCount, options );
}
IOReturn
IOBlockStorageDriver::unmap(IOService * client,
IOStorageExtent * extents,
UInt32 extentsCount,
IOStorageUnmapOptions options)
{
UInt32 extentsIndex;
IOBlockStorageDeviceExtent * extentsOut;
UInt32 extentsOutCount;
assert( sizeof( IOStorageExtent ) == sizeof( IOBlockStorageDeviceExtent ) );
if ( ( options & kIOStorageUnmapOptionReserved ) )
{
return kIOReturnBadArgument;
}
extentsOut = ( IOBlockStorageDeviceExtent * ) extents;
extentsOutCount = 0;
for ( extentsIndex = 0; extentsIndex < extentsCount; extentsIndex++ )
{
UInt64 blockStart;
UInt64 blockCount;
blockStart = ( extents[ extentsIndex ].byteStart + _mediaBlockSize - 1 ) / _mediaBlockSize;
blockCount = ( extents[ extentsIndex ].byteStart + extents[ extentsIndex ].byteCount ) / _mediaBlockSize;
if ( blockCount > blockStart )
{
blockCount = blockCount - blockStart;
extentsOut[ extentsOutCount ].blockStart = blockStart;
extentsOut[ extentsOutCount ].blockCount = blockCount;
extentsOutCount++;
}
}
if ( extentsOutCount )
{
return getProvider( )->doUnmap( extentsOut, extentsOutCount, options );
}
else
{
return kIOReturnSuccess;
}
}
IOReturn
IOBlockStorageDriver::getProvisionStatus(IOService * client,
UInt64 byteStart,
UInt64 byteCount,
UInt32 * extentsCount,
IOStorageProvisionExtent * extents,
IOStorageGetProvisionStatusOptions options)
{
UInt64 blockStart;
UInt64 blockCount;
UInt64 maxByteSize;
UInt32 extentsCountIn;
IOBlockStorageProvisionDeviceExtent * extentsOut;
IOReturn result;
if ( options != 0 )
{
return kIOReturnBadArgument;
}
maxByteSize = (_maxBlockNumber + 1 ) * _mediaBlockSize;
if ( byteStart > maxByteSize )
{
return kIOReturnBadArgument;
}
byteCount = min ( byteCount, maxByteSize - byteStart );
blockStart = byteStart / _mediaBlockSize;
blockCount = ( byteStart + byteCount + _mediaBlockSize - 1 ) / _mediaBlockSize - blockStart;
if ( ( extents == NULL ) || ( extentsCount == NULL ) || ( *extentsCount == 0 ) )
{
return kIOReturnBadArgument;
}
extentsOut = ( IOBlockStorageProvisionDeviceExtent * ) extents;
extentsCountIn = *extentsCount;
result = getProvider( )->doGetProvisionStatus( blockStart, blockCount, extentsCount, extentsOut, options );
if ( result == kIOReturnSuccess )
{
UInt32 extentsIndex;
extentsCountIn = min ( extentsCountIn, *extentsCount );
for ( extentsIndex = 0; extentsIndex < extentsCountIn; extentsIndex++ )
{
extents [ extentsIndex ].byteStart *= _mediaBlockSize;
extents [ extentsIndex ].byteCount *= _mediaBlockSize;
}
}
return result;
}
bool IOBlockStorageDriver::lockPhysicalExtents(IOService * client)
{
//
// Lock the contents of the storage object against relocation temporarily,
// for the purpose of getting physical extents.
//
return(true);
}
IOStorage * IOBlockStorageDriver::copyPhysicalExtent(IOService * client,
UInt64 * byteStart,
UInt64 * byteCount)
{
//
// Convert the specified byte offset into a physical byte offset, relative
// to a physical storage object. This call should only be made within the
// context of lockPhysicalExtents().
//
IOMedia *m;
lockForArbitration();
m = _mediaObject;
if (m) {
m->retain();
}
unlockForArbitration();
return(m);
}
void IOBlockStorageDriver::unlockPhysicalExtents(IOService * client)
{
//
// Unlock the contents of the storage object for relocation again. This
// call must balance a successful call to lockPhysicalExtents().
//
return;
}
IOReturn
IOBlockStorageDriver::setPriority(IOService * client,
IOStorageExtent * extents,
UInt32 extentsCount,
IOStoragePriority priority)
{
UInt32 extentsIndex;
IOBlockStorageDeviceExtent * extentsOut;
assert( sizeof( IOStorageExtent ) == sizeof( IOBlockStorageDeviceExtent ) );
extentsOut = ( IOBlockStorageDeviceExtent * ) extents;
for ( extentsIndex = 0; extentsIndex < extentsCount; extentsIndex++ )
{
UInt64 blockStart;
UInt64 blockCount;
blockStart = extents[ extentsIndex ].byteStart / _mediaBlockSize;
blockCount = extents[ extentsIndex ].byteCount / _mediaBlockSize;
extentsOut[ extentsIndex ].blockStart = blockStart;
extentsOut[ extentsIndex ].blockCount = blockCount;
}
return getProvider( )->doSetPriority( extentsOut, extentsCount, priority );
}
bool
IOBlockStorageDriver::validateNewMedia(void)
{
int boot_arg_value = 0;
bool boot_arg_found = PE_parse_boot_argn("disable_external_storage", &boot_arg_value, sizeof(boot_arg_value));
if (!boot_arg_found)
{
return(true);
}
if (boot_arg_value == 0)
{
return(true);
}
OSDictionary *protocol_characteristics_dictionary = OSDynamicCast(OSDictionary, getProvider()->getProperty(kIOPropertyProtocolCharacteristicsKey));
if (protocol_characteristics_dictionary)
{
OSString *interconnect_type_string = OSDynamicCast(
OSString,
protocol_characteristics_dictionary->getObject(kIOPropertyPhysicalInterconnectTypeKey));
if (interconnect_type_string)
{
if ( interconnect_type_string->isEqualTo(kIOPropertyPhysicalInterconnectTypeVirtual) )
{
return(true);
}
}
OSString *interconnect_location_string = OSDynamicCast(
OSString,
protocol_characteristics_dictionary->getObject(kIOPropertyPhysicalInterconnectLocationKey));
if (interconnect_location_string)
{
if (interconnect_location_string->isEqualTo(kIOPropertyInternalExternalKey) ||
interconnect_location_string->isEqualTo(kIOPropertyExternalKey))
{
return(false);
}
}
}
return(true);
}
// -----------------------------------------------------------------------------
// Deblocker Implementation
#include <IOKit/IOBufferMemoryDescriptor.h>
class __exported IODeblocker : public IOMemoryDescriptor
{
OSDeclareDefaultStructors(IODeblocker);
protected:
UInt64 _blockSize;
struct
{
IOMemoryDescriptor * buffer;
IOByteCount offset;
IOByteCount length;
} _chunks[3];
UInt32 _chunksCount;
IOBufferMemoryDescriptor * _excessBuffer;
UInt64 _excessCountFinal;
UInt64 _excessCountStart;
IOMemoryDescriptor * _requestBuffer;
IOStorageAttributes _requestAttributes;
IOStorageCompletion _requestCompletion;
void * _requestContext;
UInt64 _requestCount;
bool _requestIsOneBlock;
UInt64 _requestStart;
UInt64 _byteStart;
thread_call_t _threadCallback;
enum
{
kStageInit,
kStagePrepareExcessStart,
kStagePrepareExcessFinal,
kStageLast,
kStageDone
} _stage;
virtual void free() APPLE_KEXT_OVERRIDE;
virtual uint64_t getPreparationID( void ) APPLE_KEXT_OVERRIDE;
public:
static IODeblocker * withBlockSize(
UInt64 blockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext );
virtual bool initWithBlockSize(
UInt64 blockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext );
virtual addr64_t getPhysicalSegment( IOByteCount offset,
IOByteCount * length,
IOOptionBits options = 0 ) APPLE_KEXT_OVERRIDE;
virtual IOReturn prepare(IODirection forDirection = kIODirectionNone) APPLE_KEXT_OVERRIDE;
virtual IOReturn complete(IODirection forDirection = kIODirectionNone) APPLE_KEXT_OVERRIDE;
virtual bool getNextStage();
virtual IOStorageAttributes * getRequestAttributes();
virtual IOStorageCompletion * getRequestCompletion(UInt64 * actualByteCount);
virtual IOMemoryDescriptor * getRequestBuffer();
virtual void * getRequestContext();
virtual UInt64 getByteStart();
virtual thread_call_t getThreadCallback();
virtual bool setThreadCallback(thread_call_func_t callback);
};
#undef super
#define super IOMemoryDescriptor
OSDefineMetaClassAndStructors(IODeblocker, IOMemoryDescriptor)
IODeblocker * IODeblocker::withBlockSize(
UInt64 blockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext )
{
//
// Create a new IODeblocker.
//
IODeblocker * me = new IODeblocker;
if ( me && me->initWithBlockSize(
/* blockSize */ blockSize,
/* withRequestStart */ withRequestStart,
/* withRequestBuffer */ withRequestBuffer,
/* withRequestAttributes */ withRequestAttributes,
/* withRequestCompletion */ withRequestCompletion,
/* withRequestContext */ withRequestContext ) == false )
{
me->release();
me = 0;
}
return me;
}
bool IODeblocker::initWithBlockSize(
UInt64 blockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext )
{
//
// Initialize an IODeblocker.
//
// _excessCountStart = byte count from media boundary to start of request
// _excessCountFinal = byte count from end of request to a media boundary
//
IOByteCount excessBufferSize = 0;
// Ask our superclass' opinion.
if ( super::init() == false ) return false;
// Initialize our minimal state.
_blockSize = blockSize;
_chunksCount = 0;
_flags = kIODirectionNone;
_length = 0;
_requestBuffer = withRequestBuffer;
_requestBuffer->retain();
if (withRequestAttributes) _requestAttributes = *withRequestAttributes;
if (withRequestCompletion) _requestCompletion = *withRequestCompletion;
_requestContext = withRequestContext;
_requestCount = withRequestBuffer->getLength();
_requestStart = withRequestStart;
_excessCountStart = (withRequestStart ) % blockSize;
_excessCountFinal = (withRequestStart + _requestCount) % blockSize;
if ( _excessCountFinal ) _excessCountFinal = blockSize - _excessCountFinal;
_requestIsOneBlock = (_excessCountStart + _requestCount <= blockSize);
// Determine the necessary size for our scratch buffer.
switch ( _requestBuffer->getDirection() )
{
case kIODirectionIn: // (read)
{
excessBufferSize = max(_excessCountStart, _excessCountFinal);
} break;
case kIODirectionOut: // (write)
{
if ( _excessCountStart ) excessBufferSize += blockSize;
if ( _excessCountFinal ) excessBufferSize += blockSize;
// If there is excess both ends of the original request, but both
// ends reside within the same media block, then we could shorten
// our buffer size to just one block.
if ( _excessCountStart && _excessCountFinal && _requestIsOneBlock )
{
excessBufferSize -= blockSize;
}
} break;
default:
{
assert(0);
} break;
}
// Allocate our scratch buffer.
if ( excessBufferSize )
{
_excessBuffer = IOBufferMemoryDescriptor::withCapacity(
/* capacity */ excessBufferSize,
/* withDirection */ kIODirectionNone );
if ( _excessBuffer == 0 ) return false;
}
return true;
}
void IODeblocker::free()
{
//
// Free all of this object's outstanding resources.
//
if ( _requestBuffer ) _requestBuffer->release();
if ( _excessBuffer ) _excessBuffer->release();
if ( _threadCallback ) thread_call_free(_threadCallback);
super::free();
}
uint64_t
IODeblocker::getPreparationID( void )
{
uint64_t pID = kIOPreparationIDUnsupported;
if ( _chunksCount == 0 )
{
return (super::getPreparationID());
}
//
// Walk through the list of _chunks, Calling buffer getPreparationID() on
// each one of them will make sure all of them have a proper
// preparationID assigned.
//
for ( unsigned index = 0; index < _chunksCount; index++ )
{
pID = _chunks[index].buffer->getPreparationID();
}
return pID;
}
IOReturn IODeblocker::prepare(IODirection forDirection)
{
//
// Prepare the memory for an I/O transfer.
//
// This involves paging in the memory and wiring it down for the duration
// of the transfer. The complete() method finishes the processing of the
// memory after the I/O transfer finishes.
//
unsigned index;
IOReturn status = kIOReturnInternalError;
IOReturn statusUndo;
if ( forDirection == kIODirectionNone )
{
forDirection = (IODirection) (_flags & kIOMemoryDirectionMask);
}
for ( index = 0; index < _chunksCount; index++ )
{
status = _chunks[index].buffer->prepare(forDirection);
if ( status != kIOReturnSuccess ) break;
}
if ( status != kIOReturnSuccess )
{
for ( unsigned indexUndo = 0; indexUndo <= index; indexUndo++ )
{
statusUndo = _chunks[index].buffer->complete(forDirection);
assert(statusUndo == kIOReturnSuccess);
}
}
return status;
}
IOReturn IODeblocker::complete(IODirection forDirection)
{
//
// Complete processing of the memory after an I/O transfer finishes.
//
// This method shouldn't be called unless a prepare() was previously issued;
// the prepare() and complete() must occur in pairs, before and after an I/O
// transfer.
//
IOReturn status;
IOReturn statusFinal = kIOReturnSuccess;
if ( forDirection == kIODirectionNone )
{
forDirection = (IODirection) (_flags & kIOMemoryDirectionMask);
}
for ( unsigned index = 0; index < _chunksCount; index++ )
{
status = _chunks[index].buffer->complete(forDirection);
if ( status != kIOReturnSuccess ) statusFinal = status;
assert(status == kIOReturnSuccess);
}
return statusFinal;
}
addr64_t IODeblocker::getPhysicalSegment( IOByteCount offset,
IOByteCount * length,
IOOptionBits options )
{
//
// This method returns the physical address of the byte at the given offset
// into the memory, and optionally the length of the physically contiguous
// segment from that offset.
//
assert(offset <= _length);
for ( unsigned index = 0; index < _chunksCount; index++ )
{
if ( offset < _chunks[index].length )
{
addr64_t address;
address = _chunks[index].buffer->getPhysicalSegment(
/* offset */ offset + _chunks[index].offset,
/* length */ length,
/* options */ options );
if ( length ) *length = min(*length, _chunks[index].length);
return address;
}
offset -= _chunks[index].length;
}
if ( length ) *length = 0;
return 0;
}
bool IODeblocker::getNextStage()
{
//
// Obtain the next stage of the transfer. The transfer buffer will be the
// deblocker object itself and the byte start will be returned in getByteStart().
//
// This method must not be called if the current stage failed with an error
// or a short byte count, but instead getRequestCompletion() must be called
// to adjust the status and actual byte count (with respect to the original
// request) and return the original request's completion routine. The same
// call to getRequestCompletion() should also be done if the getNextStage()
// method returns false.
//
_chunksCount = 0;
_flags = (_flags & ~kIOMemoryDirectionMask) | kIODirectionNone;
_length = 0;
switch ( _requestBuffer->getDirection() )
{
case kIODirectionIn: // (read)
{
switch ( _stage )
{
case kStageInit:
{
_stage = kStageLast;
_excessBuffer->setDirection(kIODirectionIn);
_flags = (_flags & ~kIOMemoryDirectionMask) | kIODirectionIn;
_byteStart = _requestStart - _excessCountStart;
if ( _excessCountStart )
{
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _excessCountStart;
_chunksCount++;
}
_chunks[_chunksCount].buffer = _requestBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _requestBuffer->getLength();
_chunksCount++;
if ( _excessCountFinal )
{
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _excessCountFinal;
_chunksCount++;
}
} break;
case kStageLast:
{
_stage = kStageDone;
} break;
default:
{
assert(0);
} break;
} // (switch)
} break;
case kIODirectionOut: // (write)
{
switch ( _stage )
{
case kStageInit:
{
if ( _excessCountStart )
{
_stage = kStagePrepareExcessStart;
_excessBuffer->setDirection(kIODirectionIn);
_flags = (_flags & ~kIOMemoryDirectionMask) | kIODirectionIn;
_byteStart = _requestStart - _excessCountStart;
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _blockSize;
_chunksCount++;
break;
}
} // (fall thru)
case kStagePrepareExcessStart:
{
if ( _excessCountFinal )
{
// We do not issue this stage if the original transfer
// resides within one media block, and we already read
// that block into our buffer in the previous stage.
if ( !_excessCountStart || !_requestIsOneBlock )
{
_stage = kStagePrepareExcessFinal;
_excessBuffer->setDirection(kIODirectionIn);
_flags = (_flags & ~kIOMemoryDirectionMask) | kIODirectionIn;
_byteStart = _requestStart + _requestCount +
_excessCountFinal - _blockSize;
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = (_requestIsOneBlock)
? 0
: (_excessCountStart)
? _blockSize
: 0;
_chunks[_chunksCount].length = _blockSize;
_chunksCount++;
break;
}
}
} // (fall thru)
case kStagePrepareExcessFinal:
{
_stage = kStageLast;
_excessBuffer->setDirection(kIODirectionOut);
_flags = (_flags & ~kIOMemoryDirectionMask) | kIODirectionOut;
_byteStart = _requestStart - _excessCountStart;
if ( _excessCountStart )
{
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _excessCountStart;
_chunksCount++;
}
_chunks[_chunksCount].buffer = _requestBuffer;
_chunks[_chunksCount].offset = 0;
_chunks[_chunksCount].length = _requestBuffer->getLength();
_chunksCount++;
if ( _excessCountFinal )
{
_chunks[_chunksCount].buffer = _excessBuffer;
_chunks[_chunksCount].offset = (_requestIsOneBlock)
? 0
: (_excessCountStart)
? _blockSize
: 0;
_chunks[_chunksCount].offset += ( _blockSize -
_excessCountFinal );
_chunks[_chunksCount].length = _excessCountFinal;
_chunksCount++;
}
} break;
case kStageLast:
{
_stage = kStageDone;
} break;
default:
{
assert(0);
} break;
} // (switch)
} break;
default:
{
assert(0);
} break;
} // (switch)
// Determine whether we have an abort or completion condition.
if ( _chunksCount == 0 ) return false;
// Compute the total length of the descriptor over all chunks.
for ( unsigned index = 0; index < _chunksCount; index++ )
{
_length += _chunks[index].length;
}
return true;
}
IOStorageAttributes * IODeblocker::getRequestAttributes()
{
//
// Obtain the attributes for the original request.
//
return &_requestAttributes;
}
IOStorageCompletion * IODeblocker::getRequestCompletion(UInt64 * actualByteCount)
{
//
// Obtain the completion information for the original request, taking
// into account the actual byte count of the current stage.
//
switch ( _stage )
{
case kStageInit: // (inital stage)
{
*actualByteCount = 0;
} break;
case kStagePrepareExcessStart: // (write preparation stage)
case kStagePrepareExcessFinal:
{
*actualByteCount = 0;
} break;
case kStageLast: // (last stage)
case kStageDone:
{
if ( *actualByteCount > _excessCountStart )
*actualByteCount -= _excessCountStart;
else
*actualByteCount = 0;
if ( *actualByteCount > _requestBuffer->getLength() )
*actualByteCount = _requestBuffer->getLength();
} break;
default:
{
assert(0);
} break;
}
return &_requestCompletion;
}
IOMemoryDescriptor * IODeblocker::getRequestBuffer()
{
//
// Obtain the buffer for the original request.
//
return _requestBuffer;
}
void * IODeblocker::getRequestContext()
{
//
// Obtain the context for the original request.
//
return _requestContext;
}
UInt64 IODeblocker::getByteStart()
{
//
// Obtain the byte start for the current stage.
//
return _byteStart;
}
thread_call_t IODeblocker::getThreadCallback()
{
//
// Obtain the thread callback.
//
return _threadCallback;
}
bool IODeblocker::setThreadCallback(thread_call_func_t callback)
{
//
// Allocate a thread callback.
//
_threadCallback = thread_call_allocate(callback, this);
return _threadCallback ? true : false;
}
void IOBlockStorageDriver::deblockRequest(
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion,
IOBlockStorageDriver::Context * context )
{
//
// The deblockRequest method checks to see if the incoming request rests
// on the media's block boundaries, and if not, deblocks it. Deblocking
// involves rounding out the request to the nearest block boundaries and
// transferring the excess bytes into a scratch buffer.
//
// This method is part of a sequence of methods invoked for each read/write
// request. The first is prepareRequest, which allocates and prepares some
// context for the transfer; the second is deblockRequest, which aligns the
// transfer at the media's block boundaries; third is breakUpRequest, which
// breaks up the transfer into multiple sub-transfers when certain hardware
// constraints are exceeded; fourth is executeRequest, which implements the
// actual transfer from the block storage device.
//
// The current implementation of deblockRequest is asynchronous.
//
IODeblocker * deblocker;
// If the request is aligned with the media's block boundaries, we
// do short-circuit the deblocker and call breakUpRequest directly.
if ( (byteStart % context->block.size) == 0 &&
(buffer->getLength() % context->block.size) == 0 )
{
breakUpRequest(byteStart, buffer, attributes, completion, context);
return;
}
// Build a deblocker object.
deblocker = IODeblocker::withBlockSize(
/* blockSize */ context->block.size,
/* withRequestStart */ byteStart,
/* withRequestBuffer */ buffer,
/* withRequestAttributes */ attributes,
/* withRequestCompletion */ completion,
/* withRequestContext */ context );
if ( deblocker == 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.
if ( buffer->getDirection() == kIODirectionOut )
{
IOLockLock(_deblockRequestWriteLock);
_deblockRequestWriteLockCount++;
if ( _deblockRequestWriteLockCount > 1 )
{
while ( IOLockSleep( /* lock */ _deblockRequestWriteLock,
/* event */ _deblockRequestWriteLock,
/* interruptible */ THREAD_UNINT ) );
}
IOLockUnlock(_deblockRequestWriteLock);
}
// Execute the transfer (for the next stage).
deblockRequestCompletion(this, deblocker, kIOReturnSuccess, 0);
}
void IOBlockStorageDriver::deblockRequestCompletion( void * target,
void * parameter,
IOReturn status,
UInt64 actualByteCount )
{
//
// This is the completion routine for the aligned deblocker subrequests.
// It verifies the success of the just-completed stage, transitions to
// the next stage, then builds and issues a transfer for the next stage.
//
thread_call_t callback;
IODeblocker * deblocker = (IODeblocker *) parameter;
IOBlockStorageDriver * driver = (IOBlockStorageDriver *) target;
// Allocate a thread callback.
callback = deblocker->getThreadCallback();
if ( callback == 0 )
{
if ( deblocker->setThreadCallback(deblockRequestExecute) == false )
{
status = kIOReturnNoMemory;
}
}
// Determine whether an error occurred or whether there are no more stages.
if ( actualByteCount < deblocker->getLength() ||
status != kIOReturnSuccess ||
deblocker->getNextStage() == false )
{
IOStorageCompletion * completion;
// Unlock the write-lock in order to allow the next write to proceed.
if ( deblocker->getRequestBuffer()->getDirection() == kIODirectionOut )
{
IOLockLock(driver->_deblockRequestWriteLock);
driver->_deblockRequestWriteLockCount--;
if ( driver->_deblockRequestWriteLockCount > 0 )
{
IOLockWakeup( /* lock */ driver->_deblockRequestWriteLock,
/* event */ driver->_deblockRequestWriteLock,
/* one */ true );
}
IOLockUnlock(driver->_deblockRequestWriteLock);
}
// Obtain the completion information for the original request, taking
// into account the actual byte count of the current stage.
completion = deblocker->getRequestCompletion(&actualByteCount);
// Complete the original request.
IOStorage::complete(completion, status, actualByteCount);
// Release our resources.
deblocker->release();
}
else
{
// Execute the transfer (for the next stage).
if ( callback )
{
thread_call_enter1(callback, driver);
}
else
{
deblockRequestExecute(deblocker, driver);
}
}
}
void IOBlockStorageDriver::deblockRequestExecute(void * parameter, void * target)
{
//
// Execute the transfer (for the next stage).
//
IOStorageAttributes * attributes;
UInt64 byteStart;
IOStorageCompletion completion;
Context * context;
IODeblocker * deblocker = (IODeblocker *) parameter;
IOBlockStorageDriver * driver = (IOBlockStorageDriver *) target;
attributes = deblocker->getRequestAttributes();
byteStart = deblocker->getByteStart();
completion.target = driver;
completion.action = deblockRequestCompletion;
completion.parameter = deblocker;
context = (Context *) deblocker->getRequestContext();
driver->breakUpRequest(byteStart, deblocker, attributes, &completion, context);
}
// -----------------------------------------------------------------------------
// Breaker Implementation
class __exported IOBreaker : public IOSubMemoryDescriptor
{
OSDeclareDefaultStructors(IOBreaker);
protected:
UInt64 _breakSize;
UInt64 _maximumBlockCount;
UInt64 _maximumByteCount;
UInt64 _maximumSegmentCount;
UInt64 _maximumSegmentByteCount;
UInt64 _minimumSegmentAlignmentByteCount;
UInt64 _maximumSegmentWidthByteCount;
UInt64 _requestBlockSize;
IOMemoryDescriptor * _requestBuffer;
IOStorageAttributes _requestAttributes;
IOStorageCompletion _requestCompletion;
void * _requestContext;
UInt64 _requestCount;
UInt64 _requestStart;
UInt64 _byteStart;
thread_call_t _threadCallback;
virtual void free() APPLE_KEXT_OVERRIDE;
public:
static UInt64 getBreakSize(
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
IOMemoryDescriptor * withRequestBuffer,
UInt64 withRequestBufferOffset );
static IOBreaker * withBreakSize(
UInt64 breakSize,
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext );
virtual bool initWithBreakSize(
UInt64 breakSize,
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext );
virtual bool getNextStage();
virtual IOStorageAttributes * getRequestAttributes();
virtual IOStorageCompletion * getRequestCompletion(UInt64 * actualByteCount);
virtual IOMemoryDescriptor * getRequestBuffer();
virtual void * getRequestContext();
virtual UInt64 getByteStart();
virtual thread_call_t getThreadCallback();
virtual bool setThreadCallback(thread_call_func_t callback);
};
#undef super
#define super IOSubMemoryDescriptor
OSDefineMetaClassAndStructors(IOBreaker, IOSubMemoryDescriptor)
UInt64 IOBreaker::getBreakSize(
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
IOMemoryDescriptor * withRequestBuffer,
UInt64 withRequestBufferOffset )
{
//
// Determine where the next break point is given our constraints.
//
UInt64 breakSize = 0;
IOMemoryDescriptor * buffer = withRequestBuffer;
IOByteCount bufferLength = withRequestBuffer->getLength();
IOByteCount bufferOffset = withRequestBufferOffset;
addr64_t chunk = 0;
IOByteCount chunkSize = 0;
addr64_t segment = 0;
UInt32 segmentCount = 0;
IOByteCount segmentSize = 0;
// Prepare segment alignment mask.
if ( withMinimumSegmentAlignmentByteCount )
{
withMinimumSegmentAlignmentByteCount--;
}
// Constrain block count.
if ( withMaximumBlockCount )
{
UInt64 blockCountInBytes;
blockCountInBytes = withMaximumBlockCount * withRequestBlockSize;
if ( withMaximumByteCount )
{
withMaximumByteCount = min(blockCountInBytes, withMaximumByteCount);
}
else
{
withMaximumByteCount = blockCountInBytes;
}
}
// Scan the buffer's segments.
while ( segment || bufferOffset < bufferLength )
{
// Obtain a new segment.
if ( segment == 0 )
{
segment = buffer->getPhysicalSegment(bufferOffset, &segmentSize, 0);
assert(segment);
assert(segmentSize);
bufferOffset += segmentSize;
}
// Fold in a segment.
if ( chunk == 0 )
{
breakSize += segmentSize;
chunk = segment;
chunkSize = segmentSize;
segment = 0;
segmentSize = 0;
segmentCount++;
}
else if ( chunk + chunkSize == segment )
{
breakSize += segmentSize;
chunkSize += segmentSize;
segment = 0;
segmentSize = 0;
}
// Trim a complete segment.
if ( segment == 0 )
{
// Constrain segment byte count.
if ( withMaximumSegmentByteCount )
{
if ( chunkSize > withMaximumSegmentByteCount )
{
segmentSize = chunkSize - withMaximumSegmentByteCount;
}
}
// Constrain segment alignment byte count.
if ( withMinimumSegmentAlignmentByteCount )
{
if ( ( chunk & withMinimumSegmentAlignmentByteCount ) || ( chunkSize & withMinimumSegmentAlignmentByteCount ) )
{
if ( chunkSize > PAGE_SIZE )
{
segmentSize = max(chunkSize - PAGE_SIZE, segmentSize);
}
}
}
// Constrain segment width byte count.
if ( withMaximumSegmentWidthByteCount )
{
if ( chunk >= withMaximumSegmentWidthByteCount )
{
if ( chunkSize > PAGE_SIZE )
{
segmentSize = max(chunkSize - PAGE_SIZE, segmentSize);
}
}
else if ( chunk + chunkSize > withMaximumSegmentWidthByteCount )
{
segmentSize = max(chunk + chunkSize - withMaximumSegmentWidthByteCount, segmentSize);
}
}
if ( segmentSize )
{
segment = chunk + chunkSize - segmentSize;
breakSize -= segmentSize;
chunkSize -= segmentSize;
}
// Constrain byte count.
if ( withMaximumByteCount )
{
if ( breakSize >= withMaximumByteCount )
{
breakSize = withMaximumByteCount;
break;
}
}
}
// Commit a complete segment.
if ( segment )
{
// Constrain segment count.
if ( withMaximumSegmentCount )
{
if ( segmentCount == withMaximumSegmentCount )
{
break;
}
}
chunk = 0;
chunkSize = 0;
}
}
breakSize = IOTrunc(breakSize, withRequestBlockSize);
return breakSize;
}
IOBreaker * IOBreaker::withBreakSize(
UInt64 breakSize,
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext )
{
//
// Create a new IOBreaker.
//
IOBreaker * me = new IOBreaker;
if ( me && me->initWithBreakSize(
/* breakSize */ breakSize,
/* withMaximumBlockCount */ withMaximumBlockCount,
/* withMaximumByteCount */ withMaximumByteCount,
/* withMaximumSegmentCount */ withMaximumSegmentCount,
/* withMaximumSegmentByteCount */ withMaximumSegmentByteCount,
/* withMinimumSegmentAlignmentByteCount */ withMinimumSegmentAlignmentByteCount,
/* withMaximumSegmentWidthByteCount */ withMaximumSegmentWidthByteCount,
/* withRequestBlockSize */ withRequestBlockSize,
/* withRequestStart */ withRequestStart,
/* withRequestBuffer */ withRequestBuffer,
/* withRequestAttributes */ withRequestAttributes,
/* withRequestCompletion */ withRequestCompletion,
/* withRequestContext */ withRequestContext ) == false )
{
me->release();
me = 0;
}
return me;
}
bool IOBreaker::initWithBreakSize(
UInt64 breakSize,
UInt64 withMaximumBlockCount,
UInt64 withMaximumByteCount,
UInt64 withMaximumSegmentCount,
UInt64 withMaximumSegmentByteCount,
UInt64 withMinimumSegmentAlignmentByteCount,
UInt64 withMaximumSegmentWidthByteCount,
UInt64 withRequestBlockSize,
UInt64 withRequestStart,
IOMemoryDescriptor * withRequestBuffer,
IOStorageAttributes * withRequestAttributes,
IOStorageCompletion * withRequestCompletion,
void * withRequestContext )
{
//
// Initialize an IOBreaker.
//
// Ask our superclass' opinion.
if ( super::initSubRange(
/* parent */ withRequestBuffer,
/* withOffset */ 0,
/* withLength */ withRequestBuffer->getLength(),
/* withDirection */ withRequestBuffer->getDirection() ) == false )
{
return false;
}
// Initialize our minimal state.
_breakSize = breakSize;
_length = 0;
_maximumBlockCount = withMaximumBlockCount;
_maximumByteCount = withMaximumByteCount;
_maximumSegmentCount = withMaximumSegmentCount;
_maximumSegmentByteCount = withMaximumSegmentByteCount;
_minimumSegmentAlignmentByteCount = withMinimumSegmentAlignmentByteCount;
_maximumSegmentWidthByteCount = withMaximumSegmentWidthByteCount;
_requestBlockSize = withRequestBlockSize;
_requestBuffer = withRequestBuffer;
_requestBuffer->retain();
if (withRequestAttributes) _requestAttributes = *withRequestAttributes;
if (withRequestCompletion) _requestCompletion = *withRequestCompletion;
_requestContext = withRequestContext;
_requestCount = withRequestBuffer->getLength();
_requestStart = withRequestStart;
return true;
}
void IOBreaker::free()
{
//
// Free all of this object's outstanding resources.
//
if ( _requestBuffer ) _requestBuffer->release();
if ( _threadCallback ) thread_call_free(_threadCallback);
super::free();
}
bool IOBreaker::getNextStage()
{
//
// Obtain the next stage of the transfer. The transfer buffer will be the
// breaker object itself and the byte start will be returned in getByteStart().
//
// This method must not be called if the current stage failed with an error
// or a short byte count, but instead getRequestCompletion() must be called
// to adjust the status and actual byte count (with respect to the original
// request) and return the original request's completion routine. The same
// call to getRequestCompletion() should also be done if the getNextStage()
// method returns false.
//
if ( _start + _length < _requestCount )
{
_start += _length;
_length = min(_breakSize, _requestCount - _start);
_breakSize = getBreakSize(
/* withMaximumBlockCount */ _maximumBlockCount,
/* withMaximumByteCount */ _maximumByteCount,
/* withMaximumSegmentCount */ _maximumSegmentCount,
/* withMaximumSegmentByteCount */ _maximumSegmentByteCount,
/* withMinimumSegmentAlignmentByteCount */ _minimumSegmentAlignmentByteCount,
/* withMaximumSegmentWidthByteCount */ _maximumSegmentWidthByteCount,
/* withRequestBlockSize */ _requestBlockSize,
/* withRequestBuffer */ _requestBuffer,
/* withRequestBufferOffset */ _start + _length );
}
else
{
return false;
}
_byteStart = _requestStart + _start;
return true;
}
IOStorageAttributes * IOBreaker::getRequestAttributes()
{
//
// Obtain the attributes for the original request.
//
return &_requestAttributes;
}
IOStorageCompletion * IOBreaker::getRequestCompletion(UInt64 * actualByteCount)
{
//
// Obtain the completion information for the original request, taking
// into account the actual byte count of the current stage.
//
*actualByteCount += _start;
return &_requestCompletion;
}
IOMemoryDescriptor * IOBreaker::getRequestBuffer()
{
//
// Obtain the buffer for the original request.
//
return _requestBuffer;
}
void * IOBreaker::getRequestContext()
{
//
// Obtain the context for the original request.
//
return _requestContext;
}
UInt64 IOBreaker::getByteStart()
{
//
// Obtain the byte start for the current stage.
//
return _byteStart;
}
thread_call_t IOBreaker::getThreadCallback()
{
//
// Obtain the thread callback.
//
return _threadCallback;
}
bool IOBreaker::setThreadCallback(thread_call_func_t callback)
{
//
// Allocate a thread callback.
//
_threadCallback = thread_call_allocate(callback, this);
return _threadCallback ? true : false;
}
void IOBlockStorageDriver::breakUpRequest(
UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion,
IOBlockStorageDriver::Context * context )
{
//
// The breakUpRequest method checks to see if the incoming request exceeds
// our transfer constraints, and if so, breaks up the request into smaller
// sub-requests.
//
// This method is part of a sequence of methods invoked for each read/write
// request. The first is prepareRequest, which allocates and prepares some
// context for the transfer; the second is deblockRequest, which aligns the
// transfer at the media's block boundaries; third is breakUpRequest, which
// breaks up the transfer into multiple sub-transfers when certain hardware
// constraints are exceeded; fourth is executeRequest, which implements the
// actual transfer from the block storage device.
//
// The current implementation of breakUpRequest is asynchronous.
//
IOBreaker * breaker;
UInt64 breakSize;
// State our assumptions.
assert((byteStart % context->block.size) == 0);
assert((buffer->getLength() % context->block.size) == 0);
// Determine the transfer constraint, based on direction.
if ( buffer->getDirection() == kIODirectionIn )
{
breakSize = IOBreaker::getBreakSize(
/* withMaximumBlockCount */ _maxReadBlockTransfer,
/* withMaximumByteCount */ _maxReadByteTransfer,
/* withMaximumSegmentCount */ _maxReadSegmentTransfer,
/* withMaximumSegmentByteCount */ _maxReadSegmentByteTransfer,
/* withMinimumSegmentAlignmentByteCount */ _minSegmentAlignmentByteTransfer,
/* withMaximumSegmentWidthByteCount */ _maxSegmentWidthByteTransfer,
/* withRequestBlockSize */ context->block.size,
/* withRequestBuffer */ buffer,
/* withRequestBufferOffset */ 0 );
}
else
{
breakSize = IOBreaker::getBreakSize(
/* withMaximumBlockCount */ _maxWriteBlockTransfer,
/* withMaximumByteCount */ _maxWriteByteTransfer,
/* withMaximumSegmentCount */ _maxWriteSegmentTransfer,
/* withMaximumSegmentByteCount */ _maxWriteSegmentByteTransfer,
/* withMinimumSegmentAlignmentByteCount */ _minSegmentAlignmentByteTransfer,
/* withMaximumSegmentWidthByteCount */ _maxSegmentWidthByteTransfer,
/* withRequestBlockSize */ context->block.size,
/* withRequestBuffer */ buffer,
/* withRequestBufferOffset */ 0 );
}
if ( breakSize == 0 )
{
complete(completion, kIOReturnDMAError);
return;
}
// If the request doesn't exceed our transfer constaints, we do
// short-circuit the break-up and call executeRequest directly.
if ( buffer->getLength() <= breakSize )
{
executeRequest(byteStart, buffer, attributes, completion, context);
return;
}
// Build a breaker object.
if ( buffer->getDirection() == kIODirectionIn )
{
breaker = IOBreaker::withBreakSize(
/* breakSize */ breakSize,
/* withMaximumBlockCount */ _maxReadBlockTransfer,
/* withMaximumByteCount */ _maxReadByteTransfer,
/* withMaximumSegmentCount */ _maxReadSegmentTransfer,
/* withMaximumSegmentByteCount */ _maxReadSegmentByteTransfer,
/* withMinimumSegmentAlignmentByteCount */ _minSegmentAlignmentByteTransfer,
/* withMaximumSegmentWidthByteCount */ _maxSegmentWidthByteTransfer,
/* withRequestBlockSize */ context->block.size,
/* withRequestStart */ byteStart,
/* withRequestBuffer */ buffer,
/* withRequestAttributes */ attributes,
/* withRequestCompletion */ completion,
/* withRequestContext */ context );
}
else
{
breaker = IOBreaker::withBreakSize(
/* breakSize */ breakSize,
/* withMaximumBlockCount */ _maxWriteBlockTransfer,
/* withMaximumByteCount */ _maxWriteByteTransfer,
/* withMaximumSegmentCount */ _maxWriteSegmentTransfer,
/* withMaximumSegmentByteCount */ _maxWriteSegmentByteTransfer,
/* withMinimumSegmentAlignmentByteCount */ _minSegmentAlignmentByteTransfer,
/* withMaximumSegmentWidthByteCount */ _maxSegmentWidthByteTransfer,
/* withRequestBlockSize */ context->block.size,
/* withRequestStart */ byteStart,
/* withRequestBuffer */ buffer,
/* withRequestAttributes */ attributes,
/* withRequestCompletion */ completion,
/* withRequestContext */ context );
}
if ( breaker == 0 )
{
complete(completion, kIOReturnNoMemory);
return;
}
// Execute the transfer (for the next stage).
breakUpRequestCompletion(this, breaker, kIOReturnSuccess, 0);
}
void IOBlockStorageDriver::breakUpRequestCompletion( void * target,
void * parameter,
IOReturn status,
UInt64 actualByteCount )
{
//
// This is the completion routine for the broken-up breaker subrequests.
// It verifies the success of the just-completed stage, transitions to
// the next stage, then builds and issues a transfer for the next stage.
//
IOBreaker * breaker = (IOBreaker *) parameter;
thread_call_t callback;
IOBlockStorageDriver * driver = (IOBlockStorageDriver *) target;
// Allocate a thread callback.
callback = breaker->getThreadCallback();
if ( callback == 0 )
{
if ( breaker->setThreadCallback(breakUpRequestExecute) == false )
{
status = kIOReturnNoMemory;
}
}
// Determine whether an error occurred or whether there are no more stages.
if ( actualByteCount < breaker->getLength() ||
status != kIOReturnSuccess ||
breaker->getNextStage() == false )
{
IOStorageCompletion * completion;
// Obtain the completion information for the original request, taking
// into account the actual byte count of the current stage.
completion = breaker->getRequestCompletion(&actualByteCount);
// Complete the original request.
IOStorage::complete(completion, status, actualByteCount);
// Release our resources.
breaker->release();
}
else
{
// Execute the transfer (for the next stage).
if ( callback )
{
thread_call_enter1(callback, driver);
}
else
{
breakUpRequestExecute(breaker, driver);
}
}
}
void IOBlockStorageDriver::breakUpRequestExecute(void * parameter, void * target)
{
//
// Execute the transfer (for the next stage).
//
IOStorageAttributes * attributes;
IOBreaker * breaker = (IOBreaker *) parameter;
UInt64 byteStart;
IOStorageCompletion completion;
Context * context;
IOBlockStorageDriver * driver = (IOBlockStorageDriver *) target;
attributes = breaker->getRequestAttributes();
byteStart = breaker->getByteStart();
completion.target = driver;
completion.action = breakUpRequestCompletion;
completion.parameter = breaker;
context = (Context *) breaker->getRequestContext();
driver->executeRequest(byteStart, breaker, attributes, &completion, context);
}
void IOBlockStorageDriver::prepareRequest(UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageAttributes * attributes,
IOStorageCompletion * completion)
{
//
// The prepareRequest method allocates and prepares state for the transfer.
//
// This method is part of a sequence of methods invoked for each read/write
// request. The first is prepareRequest, which allocates and prepares some
// context for the transfer; the second is deblockRequest, which aligns the
// transfer at the media's block boundaries; third is breakUpRequest, which
// breaks up the transfer into multiple sub-transfers when certain hardware
// constraints are exceeded; fourth is executeRequest, which implements the
// actual transfer from the block storage device.
//
IOStorageCompletion completionOut;
Context * context;
// Determine whether the attributes are valid.
if (attributes)
{
if ((attributes->options & kIOStorageOptionReserved))
{
complete(completion, kIOReturnBadArgument);
return;
}
if (attributes->reserved0024)
{
complete(completion, kIOReturnBadArgument);
return;
}
if (attributes->reserved0032 || attributes->reserved0064)
{
complete(completion, kIOReturnBadArgument);
return;
}
}
// Allocate a context structure to hold some of our state.
context = allocateContext();
if (context == 0)
{
complete(completion, kIOReturnNoMemory);
return;
}
// Fill in the context structure with some of our state.
context->block.size = (UInt32) getMediaBlockSize();
context->block.type = kBlockTypeStandard;
context->request.byteStart = byteStart;
context->request.buffer = buffer;
context->request.buffer->retain();
if (attributes) context->request.attributes = *attributes;
if (completion) context->request.completion = *completion;
clock_get_uptime(&context->timeStart);
retain();
completionOut.target = this;
completionOut.action = prepareRequestCompletion;
completionOut.parameter = context;
if (_perfControlClient && context->perfControlContext) {
IOPerfControlClient::WorkSubmitArgs submitArgs;
submitArgs.submit_time = context->timeStart;
IOBlockStorageWorkFlags flags;
flags.isRead = (buffer->getDirection() == kIODirectionIn);
if (attributes)
flags.isLowPriority = (attributes->priority > kIOStoragePriorityDefault);
flags.ioSize = buffer->getLength();
submitArgs.driver_data = reinterpret_cast<void*>(&flags);
_perfControlClient->workSubmitAndBeginWithContext(this, context->perfControlContext, &submitArgs, nullptr);
}
// Deblock the transfer.
deblockRequest(byteStart, buffer, attributes, &completionOut, context);
}
IOReturn
IOBlockStorageDriver::requestIdle(void)
{
return(getProvider()->requestIdle());
}
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 0);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 1);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 2);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 3);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 4);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 5);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 6);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 7);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 8);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 9);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 10);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 11);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 12);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 13);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 14);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 15);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 16);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 17);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 18);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 19);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 20);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 21);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 22);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 23);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 24);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 25);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 26);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 27);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 28);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 29);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 30);
OSMetaClassDefineReservedUnused(IOBlockStorageDriver, 31);
#if TARGET_OS_OSX
extern "C" void _ZN20IOBlockStorageDriver16synchronizeCacheEP9IOService( IOBlockStorageDriver * driver, IOService * client )
{
driver->synchronize( client, 0, 0 );
}
#endif /* TARGET_OS_OSX */