Source to iokit/Families/IOHDDrive/IOHDDrive.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/IOLib.h>
#include <IOKit/IOReturn.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/assert.h>
#include <IOKit/storage/IOHDDrive.h>
#include <IOKit/storage/IOHDDriveNub.h>
#define super IODrive
OSDefineMetaClassAndStructors(IOHDDrive,IODrive)
void
gc_glue(IOService *target,void *param,UInt64 actualTransferCount,IOReturn result);
/* Accept a new piece of media, doing whatever's necessary to make it
* show up properly to the system.
*/
IOReturn
IOHDDrive::acceptNewMedia(void)
{
IOReturn result;
bool ok;
UInt64 nbytes;
UInt64 nformats;
char name[128];
bool nameSep;
lockForArbitration();
/* Since the kernel printf doesn't handle 64-bit integers, we
* simply make an assumption that the block count and size
* will be 32-bit values max.
*/
#ifdef moreDebug
IOLog("%s[IOHDDrive]::%s media: %ld blocks, %ld bytes each, write-%s.\n",
getName(),
getDeviceTypeName(),
(UInt32)_maxBlockNumber + 1,(UInt32)getMediaBlockSize(),
(_writeProtected ? "protected" : "enabled"));
#else
IOLog("%s media: %ld blocks, %ld bytes each, write-%s.\n",
getName(),
(UInt32)_maxBlockNumber + 1,(UInt32)getMediaBlockSize(),
(_writeProtected ? "protected" : "enabled"));
#endif
/* Instantiate a media object and attach it to ourselves. */
nformats = getFormatCapacities(&nbytes,1); /* get byte size of media */
name[0] = 0;
nameSep = false;
if (_provider->getVendorString()) {
strcat(name, _provider->getVendorString());
nameSep = true;
}
if (_provider->getProductString()) {
if (nameSep == true) strcat(name, " ");
strcat(name, _provider->getProductString());
nameSep = true;
}
if (nameSep == true) strcat(name, " ");
strcat(name, "Media");
result = instantiateMediaObject(&_mediaObject,0,nbytes,_mediaBlockSize,true,name);
if (result == kIOReturnSuccess) {
_mediaPresent = true;
ok = _mediaObject->attach(this); /* attach media object above us */
if (ok) {
// IOLog("IOHDDrive: registering media object\n");
_mediaObject->registerService(); /* enable matching */
}
} else {
IOLog("%s[IOHDDrive]::acceptNewMedia; couldn't instantiate media object.\n",getName());
}
unlockForArbitration();
return(result);
}
/* Allocate a new context struct. */
struct IOHDDrive::context *
IOHDDrive::allocateContext(void)
{
struct IOHDDrive::context *cx;
cx = IONew(struct IOHDDrive::context,1);
if (cx == NULL) {
return(NULL);
}
bzero(cx,sizeof(struct IOHDDrive::context));
return(cx);
}
IOReturn
IOHDDrive::checkForMedia(void)
{
IOReturn result;
bool currentState;
bool changed;
IOTakeLock(_mediaStateLock);
result = _provider->reportMediaState(¤tState,&changed);
if (result != kIOReturnSuccess) { /* the poll operation failed */
IOLog("%s[IOHDDrive]::checkForMedia; err '%s' from reportMediaState\n",
getName(),stringFromReturn(result));
IOUnlock(_mediaStateLock);
return(result);
}
/* The poll succeeded. */
if (!changed) { /* media state hasn't changed */
IOUnlock(_mediaStateLock);
return(kIOReturnSuccess);
}
/* The media has changed state. See if it's just inserted or removed. */
if (currentState == true) { /* media is now present */
// IOLog("%s[IOHDDrive]::checkForMedia; changed = %s, mediaPresent = %s\n",
// getName(),changed ? "Y" : "N",currentState ? "Y" : "N");
/* 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 */
IOUnlock(_mediaStateLock);
return(kIOReturnSuccess); /* pretend nothing happened */
}
result = recordMediaParameters(); /* learn about media */
if (result != kIOReturnSuccess) { /* couldn't record params */
initMediaStates(); /* deny existence of new media */
IOLog("%s[IOHDDrive]::checkForMedia: err '%s' from recordMediaParameters\n",
getName(),stringFromReturn(result));
IOUnlock(_mediaStateLock);
return(result);
}
/* Now we do what's necessary to make the new media
* show up properly in the system.
*/
result = acceptNewMedia();
if (result != kIOReturnSuccess) {
initMediaStates(); /* deny existence of new media */
IOLog("%s[IOHDDrive]::checkForMedia; err '%s' from acceptNewMedia\n",
getName(),stringFromReturn(result));
IOUnlock(_mediaStateLock);
return(result);
}
IOUnlock(_mediaStateLock);
return(kIOReturnSuccess); /* all done, new media is ready */
} else { /* media is now absent */
result = decommissionMedia(true); /* force a teardown */
if (result != kIOReturnSuccess && result != kIOReturnNoMedia) {
IOLog("%s[IOHDDrive]::checkForMedia; err '%s' from decommissionNewMedia\n",
getName(),stringFromReturn(result));
IOUnlock(_mediaStateLock);
return(result);
}
IOUnlock(_mediaStateLock);
return(kIOReturnSuccess); /* all done; media is gone */
}
}
UInt64
IOHDDrive::constrainByteCount(UInt64 /* requestedCount */ ,bool isWrite)
{
if (isWrite) {
return(_maxWriteByteTransfer);
} else {
return(_maxReadByteTransfer);
}
}
OSDictionary *
IOHDDrive::constructMediaProperties(void)
{
#ifdef notyet //xxx
OSDictionary *propTable;
OSData *prop;
propTable = OSDictionary::withCapacity(6);
if (propTable) {
prop = OSData::withBytes((void *)(&_mediaBlockSize),sizeof(UInt64));
if (prop) {
propTable->setObject(prop,"phys-blocksize");
prop->release(); // XXX -- svail: added.
}
prop = OSData::withBytes((void *)(&_maxBlockNumber),sizeof(UInt64));
if (prop) {
propTable->setObject(prop,"highest-block");
prop->relase(); // XXX -- svail: added.
}
prop = OSData::withBytes((void *)(&_writeProtected),sizeof(bool));
if (prop) {
propTable->setObject(prop,"write-protected");
prop->release(); // XXX -- svail: added.
}
}
return(propTable);
#else
return(0);
#endif //xxx
}
/* 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
IOHDDrive::decommissionMedia(bool forcible)
{
IOReturn result;
result = tearDown(_mediaObject);
/* If this is a forcible decommission (i.e. media is gone), we don't care
* whether the teardown worked; we forget about the media.
*/
if (forcible || (result == kIOReturnSuccess)) { /* then it tore down successfully */
_mediaObject = 0;
initMediaStates(); /* deny existence of any media */
}
return(result);
}
void
IOHDDrive::deleteContext(struct IOHDDrive::context *cx)
{
if (cx->buffer) {
cx->buffer->release(); /* reduce retain count on Memory Descriptor */
}
IODelete(cx,struct IOHDDrive::context,1);
}
IOReturn
IOHDDrive::ejectMedia(void)
{
IOReturn result;
if (_removable) {
IOTakeLock(_mediaStateLock);
result = decommissionMedia(false); /* try to teardown */
if (result == kIOReturnSuccess) { /* eject */
(void)_provider->doEjectMedia(); /* ignore any error */
}
IOUnlock(_mediaStateLock);
return(result);
} else {
return(kIOReturnUnsupported);
}
}
void
IOHDDrive::executeRequest(UInt64 byteStart,
IOMemoryDescriptor * buffer,
IOStorageCompletion completion)
{
struct IOHDDrive::context *cx;
UInt32 block;
UInt32 nblks;
IOReturn result;
_stats.clientReceived++;
if (!_mediaPresent) { /* no media? you lose */
_stats.clientCompletionsSent++;
complete(completion, kIOReturnNoMedia,0);
_stats.clientCompletionsDone++;
return;
}
result = buffer->prepare(); // (prepare the buffer)
if ( result != kIOReturnSuccess ) {
// (wiring or permissions failure?)
_stats.clientCompletionsSent++;
complete(completion, result,0);
_stats.clientCompletionsDone++;
return;
}
/* We know that we are never called with a request too large,
* nor one that is misaligned with a block.
*/
assert((byteStart % _mediaBlockSize) == 0);
assert((buffer->getLength() % _mediaBlockSize) == 0);
block = byteStart / _mediaBlockSize;
nblks = buffer->getLength() / _mediaBlockSize;
cx = allocateContext();
if (cx == NULL) {
if (buffer) {
buffer->complete();
}
_stats.clientCompletionsSent++;
complete(completion,kIOReturnNoResources,0);
_stats.clientCompletionsDone++;
return;
}
/* Copy our params to our context. */
cx->completion = completion;
cx->buffer = buffer;
cx->buffer->retain();
cx->byteStart = byteStart;
cx->byteCount = buffer->getLength();
/***
if (isWrite) {
assert(buffer->getDirection() == kIODirectionOut);
} else {
assert(buffer->getDirection() == kIODirectionIn);
}
***/
/**
IOLog("%s[IOHDDrive]::executeRequest; blk %d, nblks %d\n",getName(),
(int)block,(int)nblks);
**/
/* Now the protocol-specific provider implements the actual
* start of the data transfer: */
_stats.providerSent++;
result = _provider->doAsyncReadWrite(cx->buffer,
block,nblks,
gc_glue,
this,(void *)cx);
if (result != kIOReturnSuccess) { /* it failed to start */
_stats.providerReject++;
IOLog("%s[IOHDDrive]; executeRequest: request failed to start!\n",getName());
if (buffer) {
buffer->complete();
}
_stats.clientCompletionsSent++;
complete(completion,result,0);
_stats.clientCompletionsDone++;
deleteContext(cx);
return;
}
if (showStats()) {
IOLog("%s[IOHDDrive]; executeRequest: cr=%5d,ps=%5d,pr=%5d,pcr=%5d,ccs=%5d,ccd=%5d\n",
getName(),
_stats.clientReceived,
_stats.providerSent,
_stats.providerReject,
_stats.providerCompletionsRcvd,
_stats.clientCompletionsSent,
_stats.clientCompletionsDone);
}
}
IOReturn
IOHDDrive::formatMedia(UInt64 byteCapacity)
{
if (!_mediaPresent) {
return(kIOReturnNoMedia);
}
return(_provider->doFormatMedia(byteCapacity));
}
void
IOHDDrive::free(void)
{
if (_mediaStateLock) {
IOLockFree(_mediaStateLock);
}
super::free();
}
/* The Callback (C) entry from our provider. We just glue
* right into C++.
*/
void
gc_glue(IOService *target,void *param,UInt64 actualTransferCount,IOReturn result)
{
IOHDDrive *self;
struct IOHDDrive::context *cx;
self = (IOHDDrive *) target;
cx = (struct IOHDDrive::context *)param;
cx->result = result;
cx->actualTransferCount = actualTransferCount;
if (cx->result == kIOReturnSuccess) {
assert(cx->byteCount == cx->actualTransferCount);
}
self->RWCompletion(cx);
}
const char *
IOHDDrive::getDeviceTypeName(void)
{
return(kDeviceTypeHardDisk);
}
UInt32
IOHDDrive::getFormatCapacities(UInt64 * capacities,
UInt32 capacitiesMaxCount) const
{
return(_provider->doGetFormatCapacities(capacities,capacitiesMaxCount));
}
UInt64
IOHDDrive::getMediaBlockSize() const
{
return(_mediaBlockSize);
}
IODrive::IOMediaState
IOHDDrive::getMediaState() const
{
if (_mediaPresent) {
return(kMediaOnline);
} else {
return(kMediaOffline);
}
}
bool
IOHDDrive::handleStart(IOService * provider)
{
IOReturn result;
/* Print device name/type information on the console: */
// IOLog("%s[IOHDDrive] ",getName());
IOLog("%s drive: %s, %s, rev %s %s.\n",getName(),
_provider->getVendorString(),
_provider->getProductString(),
_provider->getRevisionString(),
_provider->getAdditionalDeviceInfoString());
/*The protocol-specific provider determines whether the media is removable. */
result = _provider->reportRemovability(&_removable);
if (result != kIOReturnSuccess) {
IOLog("%s[IOHDDrive]::handleStart; err '%s' from reportRemovability\n",
getName(),stringFromReturn(result));
return(false);
}
if (_removable) {
/* The protocol-specific provider determines whether we must poll to detect
* media insertion. Nonremovable devices never need polling.
*/
result = _provider->reportPollRequirements(&_pollIsRequired,&_pollIsExpensive);
/**
IOLog("%s[IOHDDrive]::handleStart; pollIsRequired = %s\n",
getName(),_pollIsRequired ? "Y" : "N");
**/
if (result != kIOReturnSuccess) {
IOLog("%s[IOHDDrive]::handleStart; err '%s' from reportPollRequirements\n",
getName(),stringFromReturn(result));
return(false);
}
/* The protocol-specific provider determines whether the media is ejectable
* under software control.
*/
result = _provider->reportEjectability(&_ejectable);
if (result != kIOReturnSuccess) {
IOLog("%s[IOHDDrive]::handleStart; err '%s' from reportEjectability\n",
getName(),stringFromReturn(result));
return(false);
}
/* The protocol-specific provider determines whether the media is lockable
* under software control.
*/
result = _provider->reportLockability(&_lockable);
if (result != kIOReturnSuccess) {
IOLog("%s[IOHDDrive]::handleStart; err '%s' from reportLockability\n",
getName(),stringFromReturn(result));
return(false);
}
} else { /* fixed drive: not ejectable, not lockable */
_ejectable = false;
_lockable = false;
_pollIsRequired = true; /* polling detects device disappearance */
}
/* Check for the device being ready with media inserted: */
// IOLog("%s[IOHDDrive]::handleStart; calling checkForMedia\n",getName());
result = checkForMedia();
/* The poll should never fail for nonremovable media: */
if (result != kIOReturnSuccess && !_removable) {
IOLog("%s[IOHDDrive]::handleStart: err '%s' from checkForMedia\n",
getName(),stringFromReturn(result));
return(false);
}
return(true);
}
/* The driver has been instructed to stop. If the media is writable, issue a
* Synchronize Cache command to ensure that all blocks have been written to
* the media. Then attempt to eject it.
*/
void
IOHDDrive::handleStop(IOService * provider)
{
if (_mediaPresent) {
if (!_writeProtected) {
(void)_provider->doSynchronizeCache(); /* ignore any error */
}
(void)ejectMedia(); /* eject media if it's removable */
}
super::detach(provider);
}
bool
IOHDDrive::init(OSDictionary * properties)
{
/* Do minimal initialization: */
initMediaStates();
_removable = false;
_ejectable = false;
_lockable = false;
_pollIsExpensive = false;
_pollIsRequired = false;
_mediaBlockSize = 0;
_maxBlockNumber = 0;
_maxReadByteTransfer = 0;
_maxWriteByteTransfer = 0;
bzero(&_stats,sizeof(struct stats));
_mediaStateLock = IOLockAlloc();
if (_mediaStateLock == 0) {
return false;
}
IOLockInit(_mediaStateLock);
return(super::init(properties));
}
void
IOHDDrive::initMediaStates(void)
{
_mediaPresent = false;
_writeProtected = false;
}
IOMedia *
IOHDDrive::instantiateDesiredMediaObject(void)
{
return(new IOMedia);
}
IOReturn
IOHDDrive::instantiateMediaObject(IOMedia **media,UInt64 base,UInt64 byteSize,UInt32 blockSize,
bool isWholeMedia,char *mediaName)
{
IOMedia *m;
bool result;
*media = NULL; /* just for safety */
m = instantiateDesiredMediaObject();
if (m == NULL) {
return(kIOReturnNoMemory);
}
result = m->init( base, /* base byte offset */
byteSize, /* byte size */
blockSize, /* preferred block size */
_ejectable, /* TRUE if ejectable */
isWholeMedia, /* TRUE if whole physical media */
!_writeProtected, /* TRUE if writable */
""); /* content hint */
if (result) {
*media = m;
m->setName(mediaName);
return(kIOReturnSuccess);
} else { /* some init error */
m->release();
return(kIOReturnBadArgument); /* beats me...call it this error */
}
}
bool
IOHDDrive::isMediaEjectable(void) const
{
return(_ejectable);
}
bool
IOHDDrive::isMediaPollExpensive(void) const
{
return(_pollIsExpensive);
}
bool
IOHDDrive::isMediaPollRequired(void) const
{
return(_pollIsRequired);
}
IOReturn
IOHDDrive::lockMedia(bool locked)
{
if (_lockable) {
return(_provider->doLockUnlockMedia(locked));
} else {
return(kIOReturnUnsupported);
}
}
IOReturn
IOHDDrive::pollMedia(void)
{
if (!_pollIsRequired) { /* shouldn't poll; it's an error */
return(kIOReturnUnsupported);
} else { /* poll is required...do it */
return(checkForMedia());
}
}
IOService *
IOHDDrive::probe(IOService * provider,SInt32 * score)
{
OSObject *object;
OSString *prop;
const char *string;
if (!super::probe(provider,score)) {
return(NULL);
}
/* Make sure the device type is what we expect. We can't simply use
* the class type because many nubs are subclasses of the same superclass.
*/
object = provider->getProperty(kDeviceTypeProperty);
if (object == NULL) {
IOLog("%s[IOHDDrive]:;probe; match failed: provider '%s' has no device-type property\n",
getName(),provider->getName());
return(NULL);
}
prop = OSDynamicCast(OSString,provider->getProperty(kDeviceTypeProperty));
if (prop == NULL) { /* the property doesn't exist */
IOLog("%s[IOHDDrive]:;probe; match failed: provider '%s' device-type property is wrong type\n",
getName(),provider->getName());
return(NULL);
} else { /* we got the property */
string = prop->getCStringNoCopy();
/**
IOLog("%s[IOHDDrive]:;probe; provider '%s' device-type property is '%s'\n",
getName(),provider->getName(),string);
**/
if (strcmp(string,getDeviceTypeName()) != 0) { /* the value isn't what we want */
/***
IOLog("%s[IOHDDrive]:;probe; match failed: provider '%s' device type '%s' is wrong\n",
getName(),provider->getName(),string);
***/
return(NULL);
}
/* The device type is right; now make sure the class is. */
if (setProvider(provider)) { /* the provider's class is what we expect */
// IOLog("%s[IOHDDrive]:;probe; matching provider '%s'\n",getName(),_provider->getName());
return(this);
} else { /* the provider's class is wrong, ignore it */
IOLog("%s[IOHDDrive]:;probe; match failed: provider '%s' is wrong class\n",
getName(),provider->getName());
return(NULL);
}
}
}
IOReturn
IOHDDrive::recordAdditionalMediaParameters(void)
{
return(kIOReturnSuccess); /* default does nothing */
}
IOReturn
IOHDDrive::recordMediaParameters(void)
{
IOReturn result;
OSDictionary *propTable;
/* Determine the device's block size and max block number.
* What should an unformatted device report? All zeroes, or an error?
*/
result = _provider->reportBlockSize(&_mediaBlockSize);
if (result != kIOReturnSuccess) {
goto err;
}
result = _provider->reportMaxValidBlock(&_maxBlockNumber);
if (result != kIOReturnSuccess) {
goto err;
}
/* Calculate the maximum allowed byte transfers for reads and writes. */
result = _provider->reportMaxReadTransfer(_mediaBlockSize,&_maxReadByteTransfer);
if (result != kIOReturnSuccess) {
goto err;
}
result = _provider->reportMaxWriteTransfer(_mediaBlockSize,&_maxWriteByteTransfer);
if (result != kIOReturnSuccess) {
goto err;
}
/* Is the media write-protected? */
result = _provider->reportWriteProtection(&_writeProtected);
if (result != kIOReturnSuccess) {
goto err;
}
/* Create media-related properties: */
propTable = constructMediaProperties();
// xxx what to do with propTable? xxx
/* Record additional media-related information,
* and perhaps create properties.
*/
result = recordAdditionalMediaParameters();
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:;
_mediaPresent = false;
_writeProtected = true;
return(result);
}
void
IOHDDrive::rejectMedia(void)
{
(void)_provider->doEjectMedia(); /* eject it, ignoring any error */
initMediaStates(); /* deny existence of new media */
}
void
IOHDDrive::RWCompletion(struct IOHDDrive::context *cx)
{
bool isWrite;
_stats.providerCompletionsRcvd++;
if (cx->result == kIOReturnSuccess) {
assert(cx->byteCount == cx->actualTransferCount);
} else {
IOLog("%s[IOHDDrive]::RWCompletion; result = %s, req=%d, actual=%d\n",
getName(),stringFromReturn(cx->result),(int)cx->byteCount,
(int)cx->actualTransferCount);
}
if (cx->buffer->getDirection() == kIODirectionOut) {
isWrite = true;
} else {
isWrite = false;
}
addToBytesTransferred(cx->actualTransferCount,0,0,isWrite);
if (cx->result != kIOReturnSuccess) {
incrementErrors(isWrite);
}
if (cx->buffer) {
cx->buffer->complete();
}
/* Complete the client request, which was issued at executeRequest: */
assert(cx->completion.action);
_stats.clientCompletionsSent++;
complete(cx->completion,cx->result,cx->actualTransferCount);
_stats.clientCompletionsDone++;
if (showStats()) {
IOLog("%s[IOHDDrive]; RWCompletion: cr=%5d,ps=%5d,pr=%5d,pcr=%5d,ccs=%5d,ccd=%5d\n",
getName(),
_stats.clientReceived,
_stats.providerSent,
_stats.providerReject,
_stats.providerCompletionsRcvd,
_stats.clientCompletionsSent,
_stats.clientCompletionsDone);
}
deleteContext(cx);
}
bool
IOHDDrive::setProvider(IOService * provider)
{
_provider = OSDynamicCast(IOHDDriveNub,provider);
if (_provider == NULL) {
return(false);
} else {
return(true);
}
}
bool
IOHDDrive::showStats(void)
{
return(false);
}
/* Tear down the stack above the specified object. Usually these objects will
* be of type IOMedia, but they could be any IOService.
*/
IOReturn
IOHDDrive::tearDown(IOService *media)
{
IOReturn result;
lockForArbitration();
if (media) {
if (media->terminate()) {
media->release();
initMediaStates(); /* clear all knowledge of the media */
result = kIOReturnSuccess;
} else {
result = kIOReturnBusy;
}
} else {
result = kIOReturnNoMedia;
}
unlockForArbitration();
return(result);
}
bool
IOHDDrive::validateNewMedia(void)
{
return(true);
}