Source to iokit/Families/IOSCSIHDDrive/IOBasicSCSI.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/scsi/IOSCSIDeviceInterface.h>
#include <IOKit/storage/scsi/IOBasicSCSI.h>
/* xxx to do:
* Consider a way to report to the superclass that this
* IO should be retried. (A return status from asyncReadWrite?)
* As for internal IOs that need retry due, for example, to a
* UA, how do we handle that in this class? Is the code in
* genericCompletion OK, even if it's ugly?
*
*/
#define super IOService
OSDefineMetaClass(IOBasicSCSI,IOService)
OSDefineAbstractStructors(IOBasicSCSI,IOService)
void IOBasicSCSI_gc_glue(IOService *object,void *param);
/* Allocate a new context struct. A return of NULL means we couldn't
* allocate either the context itself or one of its members.
*/
struct IOBasicSCSI::context *
IOBasicSCSI::allocateContext(void)
{
struct context *cx;
//xxx IOLog("allocateContext entered\n");
/* First, the context structure itself. */
cx = IONew(struct context,1);
if (cx == NULL) {
return(NULL);
}
bzero(cx,sizeof(struct context));
/* Allocate all the structs and objects we need. If any allocation
* fails, we can simply call deleteContext() to free anything
* allocated so far.
*/
cx->scsireq = _provider->allocCommand(kIOSCSIDevice, 0);
if (cx->scsireq == NULL) {
deleteContext(cx);
return(NULL);
}
/* Preset the completion parameters, which are the same for
* all SCSI requests we issue. Only the target function changes.
*/
// cx->scsireq->completionClient = this;
// cx->scsireq->completionParam = cx;
/**
IOLog("allocateContext: completionClient = %08x, Param = %08x\n",
(unsigned int)cx->scsireq->completionClient,
(unsigned int)cx->scsireq->completionParam);
**/
cx->senseData = (SCSISenseData *)IOMalloc(256);
if (cx-> senseData == NULL) {
deleteContext(cx);
return(NULL);
}
bzero(cx->senseData, 256 );
cx->senseDataDesc = IOMemoryDescriptor::withAddress(cx->senseData,
256,
kIODirectionIn);
cx->sync = IOSyncer::create(false);
if (cx->sync == NULL) {
deleteContext(cx);
return(NULL);
}
/* We defer allocation of the Memory Descriptor till later;
* it will be allocated where it's needed.
*/
// IOLog("allocateContext returning cx = %08x\n",(unsigned int)cx);
return(cx);
}
IOReturn
IOBasicSCSI::allocateInquiryBuffer(UInt8 **buf,UInt32 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::allocateTempBuffer(UInt8 **buf,UInt32 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::allocateReadCapacityBuffer(UInt8 **buf,UInt8 size)
{
*buf = (UInt8 *)IOMalloc(size);
if (*buf == NULL) {
return(kIOReturnNoMemory);
}
bzero(*buf,size);
return(kIOReturnSuccess);
}
UInt32
IOBasicSCSI::createReadCdb(UInt8 *cdb,UInt32 *cdbLength,
UInt32 block,UInt32 nblks,
UInt32 *maxAutoSenseLength,
UInt32 *timeoutSeconds)
{
struct IORWcdb *c;
c = (struct IORWcdb *)cdb;
c->opcode = SOP_READ10;
c->lunbits = 0;
c->lba_3 = block >> 24;
c->lba_2 = block >> 16;
c->lba_1 = block >> 8;
c->lba_0 = block & 0xff;
c->reserved = 0;
c->count_msb = nblks >> 8;
c->count_lsb = nblks & 0xff;
c->ctlbyte = 0;
*cdbLength = 10;
*maxAutoSenseLength = 8; /* do the sense */
*timeoutSeconds = 60;
return(0);
}
UInt32
IOBasicSCSI::createWriteCdb(UInt8 *cdb,UInt32 *cdbLength,
UInt32 block,UInt32 nblks,
UInt32 *maxAutoSenseLength,
UInt32 *timeoutSeconds)
{
struct IORWcdb *c;
c = (struct IORWcdb *)cdb;
c->opcode = SOP_WRITE10;
c->lunbits = 0;
c->lba_3 = block >> 24;
c->lba_2 = block >> 16;
c->lba_1 = block >> 8;
c->lba_0 = block & 0xff;
c->reserved = 0;
c->count_msb = nblks >> 8;
c->count_lsb = nblks & 0xff;
c->ctlbyte = 0;
*cdbLength = 10;
*maxAutoSenseLength = sizeof( SCSISenseData ); /* do the sense */
*timeoutSeconds = 60;
return(0);
}
void
IOBasicSCSI::deleteContext(struct context *cx)
{
// IOLog("deleteContext %08x\n",(unsigned int)cx);
if (cx->scsireq) {
cx->scsireq->release();
}
// if (cx->scsiresult) {
// IODelete(cx->scsiresult,struct IOSCSIResult,1);
// }
if (cx->senseData)
{
IOFree( cx->senseData, 256 );
}
if ( cx->senseDataDesc )
{
cx->senseDataDesc->release();
}
if (cx->memory) {
cx->memory->release();
}
if (cx->sync) {
cx->sync->release();
}
IODelete(cx,struct context,1);
}
void
IOBasicSCSI::deleteInquiryBuffer(UInt8 *buf,UInt32 size)
{
IOFree((void *)buf,size);
}
void
IOBasicSCSI::deleteTempBuffer(UInt8 *buf,UInt32 len)
{
IOFree((void *)buf,len);
}
void
IOBasicSCSI::deleteReadCapacityBuffer(UInt8 *buf,UInt32 len)
{
IOFree((void *)buf,len);
}
#if 1
IOReturn
IOBasicSCSI::doInquiry(UInt8 *inqBuf,UInt32 maxLen,UInt32 *actualLen)
{
_provider->getInquiryData( inqBuf, maxLen, actualLen );
return kIOReturnSuccess;
}
#else
IOReturn
IOBasicSCSI::doInquiry(UInt8 *inqBuf,UInt32 maxLen,UInt32 *actualLen)
{
struct context *cx;
struct IOInquirycdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
SCSIResults scsiResults;
UInt8 len;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
/* First, we do a minimal Inquiry, which all devices support. This will
* allow us to determine the full size of an Inquiry from this device.
* We then issue the full-length Inquiry immediately to get all the
* available data.
*/
if ((int)maxLen < kMinInqSize) { /* buffer too small: programming error! */
return(kIOReturnNoMemory);
}
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOInquirycdb *)&scsiCDB.cdb;
c->opcode = SOP_INQUIRY;
c->lunbits = 0;
c->pagecode = 0;
c->reserved = 0;
c->len = kMinInqSize;
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
scsiCDB.cdbFlags = 0;
req->setCDB( &scsiCDB );
req->setTimeout( 1000 );
cx->memory = IOMemoryDescriptor::withAddress(inqBuf,
kMinInqSize,
kIODirectionIn);
req->setPointers( cx->memory, c->len, false );
queueCommand(cx,kSync,getInquiryPowerState()); /* queue and possibly wait for power */
result = simpleSynchIO(cx);
if (result == kIOReturnSuccess) { /* the minimal inquiry succeeded */
/* Now obtain the full length and do a full inquiry: */
len = inqBuf[4];
if (len != 0) { /* there's additional data to get */
len += 4;
if (len > maxLen) { /* only fill to end of buffer */
len = maxLen;
}
c->len = len;
req->setCDB( &scsiCDB );
req->setPointers( cx->memory, c->len, false );
if (cx->memory->initWithAddress(inqBuf, len, kIODirectionIn)) {
/* memory descriptor was successfully reset with new length */
result = simpleSynchIO(cx);
}
}
}
req->getResults( &scsiResults );
*actualLen = scsiResults.bytesTransferred;
deleteContext(cx);
return(result);
}
#endif
IOReturn
IOBasicSCSI::doReadCapacity(UInt64 *blockSize,UInt64 *maxBlock)
{
struct context *cx;
struct IOReadCapcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt8 *buf;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOReadCapcdb *)&scsiCDB.cdb;
c->opcode = SOP_READCAP;
c->lunbits = 0;
c->lba_3 = 0;
c->lba_2 = 0;
c->lba_1 = 0;
c->lba_0 = 0;
c->reserved1 = 0;
c->reserved2 = 0;
c->reserved3 = 0;
c->ctlbyte = 0;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true );
req->setTimeout( 5000 );
*blockSize = 0;
*maxBlock = 0;
result = allocateReadCapacityBuffer(&buf,kReadCapSize);
if (result == kIOReturnSuccess) {
cx->memory = IOMemoryDescriptor::withAddress((void *)buf,
kReadCapSize,
kIODirectionIn);
req->setPointers( cx->memory, kReadCapSize, false );
/* We force the drive to be completely powered-up, including the mechanical
* components, because some drives (e.g. CDs) access the media.
*/
queueCommand(cx,kSync,getReadCapacityPowerState()); /* queue the operation, sleep awaiting power */
result = simpleSynchIO(cx);
if (result == kIOReturnSuccess) {
*blockSize = (buf[4] << 24) | /* endian-neutral */
(buf[5] << 16) |
(buf[6] << 8) |
(buf[7] );
*maxBlock = (buf[0] << 24) | /* endian-neutral */
(buf[1] << 16) |
(buf[2] << 8) |
(buf[3] );
}
deleteReadCapacityBuffer(buf,kReadCapSize);
}
deleteContext(cx);
return(result);
}
IOReturn
IOBasicSCSI::executeCdb(struct cdbParams *params)
{
struct context *cx;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
SCSIResults scsiResults;
IOReturn result;
bool isWrite;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
if (params->cdbLength > 16) {
params->cdbLength = 16;
}
bcopy(params->cdb,(char *)&scsiCDB.cdb[0],params->cdbLength);
scsiCDB.cdbLength = params->cdbLength;
scsiCDB.cdbFlags = 0;
if (params->senseLength > 255) {
params->senseLength = 255;
}
req->setCDB(&scsiCDB);
req->setPointers(params->senseBuffer,params->senseLength,false,true);
req->setTimeout(params->timeoutSeconds * 1000);
cx->memory = params->dataBuffer;
cx->memory->retain();
isWrite = false;
if (params->dataLength != 0) {
if (cx->memory->getDirection() == kIODirectionIn) {
isWrite = false;
}
}
req->setPointers(cx->memory,params->dataLength,isWrite);
queueCommand(cx,kSync,getExecuteCDBPowerState()); /* queue the operation awaiting power */
result = simpleSynchIO(cx); /* issue a simple command */
req->getResults(&scsiResults);
/* Now we return the sense data actual transfer counts, and device status. */
if (scsiResults.requestSenseDone == true) { /* we have sense data to copy */
params->actualSenseLength = scsiResults.requestSenseLength;
}
params->status = scsiResults.scsiStatus; /* status */
params->actualDataLength = scsiResults.bytesTransferred;
deleteContext(cx);
return(result);
}
void
IOBasicSCSI::free(void)
{
if (_inqBuf) {
deleteInquiryBuffer(_inqBuf,_inqBufSize);
_inqBuf = NULL;
}
#ifdef DISKPM
if (_powerQueue.lock) {
IOLockFree(_powerQueue.lock);
}
#endif
super::free();
}
/* The Callback (C) entry from the SCSI provider. We just glue
* right into C++.
*/
void
IOBasicSCSI_gc_glue(IOService *object,void *param)
{
IOBasicSCSI *self;
struct IOBasicSCSI::context *cx;
self = (IOBasicSCSI *)object;
cx = (struct IOBasicSCSI::context *)param;
self->genericCompletion(cx); /* do it in C++ */
}
void
IOBasicSCSI::genericCompletion(struct IOBasicSCSI::context *cx)
{
struct context *origcx;
SCSIResults scsiResults;
/**
IOLog("%s[IOBasicSCSI]::genericCompletion, state = %s\n",
getName(),stringFromState(cx->state));
**/
/* If we're handling a Unit-Attention condition, continue looping
* until handleUnitAttention decides we're finished.
*/
if (cx->state == kHandlingUnitAttention) {
cx->step++; /* we're at the next step */
//IOLog(" ::genericCompletion: recalling handleUnitAttention, step = %ld\n",cx->step);
handleUnitAttention(cx);
} else { /* might be a new UA */
/* If we're not currently handling a Unit Attention, see if
* we just got one to handle.
*/
cx->scsireq->getResults( &scsiResults );
/**
IOLog("%s[IOBasicSCSI]::genericCompletion: result = %s\n",
getName(),stringFromReturn(result));
**/
/* A special case is Unit Attention, which can happen at any time. We
* call handleUnitAttention() and allow that function to switch states
* so that we can issue multiple asynch commands to restore the device
* condition. After handleUnitAttention() completes, it sets the
* state to doRetry, so we retry the original command.
*/
if (scsiResults.requestSenseDone == true) { /* an error occurred */
/**
IOLog("%s[IOBasicSCSI]::genericCompletion: sense code %02x\n",
getName(),cx->scsiresult->scsiSense[02]);
**/
if ((cx->senseData->senseKey & 0x0f) == kUnitAttention) {
/**
IOLog("%s[IOBasicSCSI]::genericCompletion: starting UnitAttention process\n",
getName());
**/
/* Save original IO context, set state and step, and
* begin handling the Unit Attention condition. If
* we can't allocate a new context for use by
* handleUnitAttention, then the original IO will be
* completed with the Unit-Attention error. Note that
* we must actually change cx to the new context, so that
* we work properly below if handleUnitAttention simply
* returns without doing anything.
*/
origcx = cx;
cx = allocateContext();
if (cx == NULL) { /* ugh. forget about it */
goto noUA;
}
cx->originalIOContext = origcx;
cx->state = kHandlingUnitAttention;
cx->step = 1;
//IOLog(" ::genericCompletion: calling handleUnitAttention, step = %ld\n",cx->step);
handleUnitAttention(cx);
}
}
}
noUA:
/* At this point we dispatch the completion depending on our state.
* Note that we might still have to continue handling a Unit Attention
* if the call to handleUnitAttention isn't yet finished. In this case
* we just return, and get re-entered on the completion of the command
* that was issued by handleUnitAttention.
*/
/**
IOLog("%s[IOBasicSCSI]::genericCompletion: dispatching, state = %s\n",
getName(),stringFromState(cx->state));
**/
switch (cx->state) {
case kSimpleSynchIO :
cx->sync->signal(kIOReturnSuccess, false); /* Just wake up the waiting thread: */
break;
case kDoneHandlingUnitAttention : /* switch back to original command & retry */
//IOLog(" ::genericCompletion: done handling UnitAttention\n");
origcx = cx->originalIOContext;
deleteContext(cx);
#if 0
if (origcx->scsireq->completionAction == NULL) {
IOPanic("IOBasicSCSI::genericCompletion: completionAction is NULL!");
}
#endif
origcx->scsireq->execute();
break;
case kAsyncReadWrite : /* normal r/w completion */
RWCompletion(cx);
deleteContext(cx);
break;
case kHandlingUnitAttention : /* still handling UA */
break; /* just wait for next completion */
case kNone : /* undefined */
case kMaxStateValue :
case kAwaitingPower :
break;
}
return;
}
char *
IOBasicSCSI::getAdditionalDeviceInfoString(void)
{
return("[SCSI]");
}
UInt64
IOBasicSCSI::getBlockSize(void)
{
return(_blockSize);
}
char *
IOBasicSCSI::getProductString(void)
{
return(_product);
}
char *
IOBasicSCSI::getRevisionString(void)
{
return(_rev);
}
char *
IOBasicSCSI::getVendorString(void)
{
return(_vendor);
}
/* The default implementation doesn't have anything to do for Unit-Attention,
* so we just set the state to retry the original command.
*/
void
IOBasicSCSI::handleUnitAttention(struct context *cx)
{
//IOLog("IOBasicSCSI::handleUnitAttenion, step = %ld\n",cx->step);
//IOLog("IOBasicSCSI::handleUnitAttenion, nothing to do\n" );
cx->state = kDoneHandlingUnitAttention;
}
bool
IOBasicSCSI::init(OSDictionary * properties)
{
_inqBuf = NULL;
_inqBufSize = 0;
_inqLen = 0;
_vendor[8] = '\0';
_product[16] = '\0';
_rev[4] = '\0';
_readCapDone = false;
_blockSize = 0;
_maxBlock = 0;
_removable = false;
#ifdef DISKPM
_powerQueue.head = NULL;
_powerQueue.tail = NULL;
_powerQueue.lock = IOLockAlloc();
if (_powerQueue.lock == NULL) {
return(false);
}
IOLockInit(_powerQueue.lock);
#endif
return(super::init(properties));
}
IOService *
IOBasicSCSI::probe(IOService * provider,SInt32 * score)
{
IOReturn result;
if (!super::probe(provider,score)) {
return(NULL);
}
_provider = (IOSCSIDevice *)provider;
/* Do an inquiry to get the device type. The inquiry buffer will
* be deleted by free().
*/
_inqBufSize = kMaxInqSize;
result = allocateInquiryBuffer(&_inqBuf,_inqBufSize);
if (result != kIOReturnSuccess) {
return(NULL);
}
result = doInquiry(_inqBuf,_inqBufSize,&_inqLen);
if (result != kIOReturnSuccess) {
return(NULL);
}
#ifdef notdef
// xxx NEVER match for ID=0, the boot disk. This lets us
// test this driver on other disk drives.
//
if (_provider->getTarget() == 0) {
IOLog("**%s[IOBasicSCSI]:probe; ignoring SCSI ID %d\n",
getName(),(int)_provider->getTarget());
return(NULL);
}
#endif
if (deviceTypeMatches(_inqBuf,_inqLen)) {
OSString * string;
// Fetch SCSI device information from the nub.
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyVendorName));
if (string) {
strncpy(_vendor, string->getCStringNoCopy(), 8);
_vendor[8] = '\0';
}
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyProductName));
if (string) {
strncpy(_product, string->getCStringNoCopy(), 16);
_product[16] = '\0';
}
string = OSDynamicCast(OSString,
_provider->getProperty(kSCSIPropertyProductRevision));
if (string) {
strncpy(_rev, string->getCStringNoCopy(), 4);
_rev[4] = '\0';
}
/***
IOLog("**%s[IOBasicSCSI]::probe; accepting %s, %s, %s, %s; SCSI ID %d\n",
getName(),getVendorString(),getProductString(),getRevisionString(),
getAdditionalDeviceInfoString(),
(int)_provider->getTarget());
***/
return(this);
} else {
return(NULL);
}
}
void
IOBasicSCSI::dequeueCommands(void)
{
#ifdef DISKPM
struct queue *q;
IOReturn result;
q = &_powerQueue;
IOTakeLock(q->lock);
/* Dequeue and execute all requests for which we have the proper power level. */
while (q->head) {
cx = q->head;
if (pm_vars->myCurrentState != cx->desiredPower) {
break;
}
q->head = cx->next; /* remove command from the queue */
if (q->head == NULL) {
q->tail = NULL;
}
cx->state = kNone;
/* If the queued request was synchronous, all we have to do is wake it up. */
if (cx->isSync) {
cx->sync->signal(kIOReturnSuccess, false); /* Just wake up the waiting thread: */
} else { /* it's async; fire it off! */
result = standardAsyncReadWriteExecute(cx); /* execute the async IO */
if (result != kIOReturnSuccess) { /* provider didn't accept it! */
RWCompletion(cx); /* force a completion */
}
}
};
IOUnlock(q->lock);
#endif
}
void
IOBasicSCSI::queueCommand(struct context *cx,bool isSync,UInt32 desiredPower)
{
#ifndef DISKPM //for now, just return immediately without queueing
/* If we're ifdefed out, we have to start async requests. Sync requests
* will just return immediately without any delay for power.
*/
if (isSync == kAsync) {
(void)standardAsyncReadWriteExecute(cx); /* execute the async IO */
}
#else
struct queue *q;
/* First, we enqueue the request to ensure sequencing with respect
* to other commands that may already be in the queue.
*/
q = &_powerQueue;
cx->next = NULL;
cx->state = kAwaitingPower;
IOTakeLock(q->lock);
if (q->head == NULL) { /* empty queue */
q->head = cx;
q->tail = q->head;
} else { /* not empty; add after tail */
q->tail->next = cx;
q->tail = cx;
}
/* If the command is synchronous, start by assuming we'll have to sleep
* awaiting power (and subsequent dequeuing). If, however, power is already
* right, then dequeuCommands will unlock the lock and we will continue,
* returning inline to the call site, exactly as if we were awakened.
*
* An async request will call dequeueCommands and always return immediately.
*/
IOUnlock(q->lock);
/* Now we try to dequeue pending commands if the power's right. */
dequeueCommands();
/* If we're synchronous, we'll wait here till dequeued. If we were
* dequeued above (and unlocked), then we'll return to allow the
* caller to continue with the command execution.
*/
if (isSync) {
cx->sync->wait(false); /* waits here till awakened */
}
#endif //DISKPM
}
IOReturn
IOBasicSCSI::reportBlockSize(UInt64 *blockSize)
{
IOReturn result;
*blockSize = 0;
result = kIOReturnSuccess;
if (_readCapDone == false) {
result = doReadCapacity(&_blockSize,&_maxBlock);
_readCapDone = true;
}
if (result == kIOReturnSuccess) {
*blockSize = _blockSize;
}
return(result);
}
IOReturn
IOBasicSCSI::reportEjectability(bool *isEjectable)
{
*isEjectable = true; /* default: if it's removable, it's ejectable */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportLockability(bool *isLockable)
{
*isLockable = true; /* default: if it's removable, it's lockable */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportMaxReadTransfer (UInt64 blocksize,UInt64 *max)
{
*max = blocksize * 65536; /* max blocks in a SCSI transfer */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportMaxValidBlock(UInt64 *maxBlock)
{
IOReturn result;
*maxBlock = 0;
result = kIOReturnSuccess;
if (_readCapDone == false) {
result = doReadCapacity(&_blockSize,&_maxBlock);
_readCapDone = true;
}
if (result == kIOReturnSuccess) {
*maxBlock = _maxBlock;
}
return(result);
}
IOReturn
IOBasicSCSI::reportMaxWriteTransfer(UInt64 blocksize,UInt64 *max)
{
*max = blocksize * 65536; /* max blocks in a SCSI transfer */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportPollRequirements(bool *pollRequired,bool *pollIsExpensive)
{
*pollIsExpensive = false;
*pollRequired = _removable; /* for now, all removables need polling */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::reportRemovability(bool *isRemovable)
{
if (_inqLen > 0) { /* inquiry byte exists to check */
if (_inqBuf[1] & 0x80) { /* it's removable */
*isRemovable = true;
_removable = true;
} else { /* it's not removable */
*isRemovable = false;
_removable = false;
}
} else { /* no byte? call it nonremovable */
*isRemovable = false;
}
return(kIOReturnSuccess);
}
/* Issue a Mode Sense to get the Mode Parameter Header but no pages.
* Since we're only interested in the Mode Parameter Header, we just
* issue a standard SCSI-1 6-byte command, nothing fancy.
*/
IOReturn
IOBasicSCSI::reportWriteProtection(bool *writeProtected)
{
struct context *cx;
struct IOModeSensecdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
SCSIResults scsiResults;
UInt8 *buf;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOModeSensecdb *)&scsiCDB.cdb;
c->opcode = SOP_MODESENSE;
c->lunbits = 0;
c->pagecode = 0 | 0x01; /* get current settings; any page will work */
c->reserved = 0;
c->len = kModeSenseSize;
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, sizeof(SCSISenseData), false, true );
req->setTimeout( 1000 );
result = allocateTempBuffer(&buf,kModeSenseSize);
if (result == kIOReturnSuccess) {
cx->memory = IOMemoryDescriptor::withAddress((void *)buf,
kModeSenseSize,
kIODirectionIn);
req->setPointers( cx->memory, kModeSenseSize, false );
queueCommand(cx,kSync,getReportWriteProtectionPowerState()); /* queue the op, sleep awaiting power */
result = simpleSynchIO(cx);
if (result == kIOReturnUnderrun) {
cx->scsireq->getResults( &scsiResults );
if (scsiResults.bytesTransferred >= 4)
result = kIOReturnSuccess;
}
if (result == kIOReturnSuccess) {
if (buf[2] & 0x80) {
*writeProtected = true;
} else {
*writeProtected = false;
}
}
deleteTempBuffer(buf,kModeSenseSize);
}
deleteContext(cx);
return(result);
}
/* Issue a simple, synchronous SCSI operation. The caller's supplied context
* contains a SCSI command and Memory Descriptor. The caller is responsible
* for deleting the context.
*/
IOReturn
IOBasicSCSI::simpleSynchIO(struct context *cx)
{
IOSCSICommand *req;
IOReturn result;
if (cx == NULL) { /* safety check */
return(kIOReturnNoMemory);
}
/* Set completion to return to genericCompletion: */
req = cx->scsireq;
req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx );
cx->state = kSimpleSynchIO;
/**
IOLog("%s[IOBasicSCSI]::simpleSynchIO; issuing SCSI cmd %02x\n",
getName(),req->cdb.byte[0]);
**/
/* Start the scsi request: */
//IOLog("IOBasicSCSI::simpleSynchIO, lock initted, calling SCSI\n");
#if 0
if (req->completionAction == NULL) {
IOPanic("IOBasicSCSI::simpleSynchIO: completionAction is NULL!");
}
if (req->completionClient == NULL) {
IOPanic("IOBasicSCSI::simpleSynchIO: completionClient is NULL!");
}
if (req->completionParam == NULL) {
IOPanic("IOBasicSCSI::simpleSynchIO: completionParam is NULL!");
}
#endif
result = req->execute();
// result = _provider->executeRequest(req,cx->memory,cx->scsiresult);
if (result == true ) {
// IOLog("IOBasicSCSI::simpleSynchIO, SCSI req accepted\n");
/* Wait for it to complete by attempting to acquire a read-lock, which
* will block until the write-lock is released by the completion routine.
*/
cx->sync->wait(false); /* waits here till unlocked at completion */
/* We're back: */
result = req->getResults((SCSIResults *) 0);
/**
if ((result != kIOReturnSuccess) && (result != 10007)) {
IOLog("%s[IOBasicSCSI]::simpleSynchIO; err '%s' from completed req\n",
getName(),stringFromReturn(result));
}
**/
} else {
/**
IOLog("%s[IOBasicSCSI]:simpleSynchIO; err '%s' queueing SCSI req\n",
getName(),stringFromReturn(result));
**/
}
// IOLog("IOBasicSCSI: completed; result '%s'\n",stringFromReturn(result));
return(result);
}
IOReturn
IOBasicSCSI::standardAsyncReadWrite(IOMemoryDescriptor *buffer,
UInt32 block,UInt32 nblks,
gdCompletionFunction action,
IOService *target,void *param)
{
struct context *cx;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt32 reqSenseLength;
UInt32 timeoutSeconds;
UInt8 *cdb;
bool isWrite;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
buffer->retain(); /* bump the retain count */
cx->memory = buffer;
if (buffer->getDirection() == kIODirectionOut) {
isWrite = true;
} else {
isWrite = false;
}
/**
IOLog("%s[IOBasicSCSI]::standardAsyncReadWrite; (%s) blk %ld nblks %ld\n",
getName(),(isWrite ? "write" : "read"),block,nblks);
**/
req = cx->scsireq;
/* Set completion to return to rwCompletion: */
cx->completion.action = action;
cx->completion.target = target;
cx->completion.param = param;
bzero( &scsiCDB, sizeof(scsiCDB) );
req->setPointers( buffer, nblks * getBlockSize(), isWrite );
req->setCallback( (void *)this, (CallbackFn)IOBasicSCSI_gc_glue, (void *)cx );
cx->state = kAsyncReadWrite;
cdb = (UInt8 *) &scsiCDB.cdb;
/* Allow a subclass to override the creation of the cdb and specify
* other parameters for the operation.
*/
if (isWrite) {
scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&timeoutSeconds);
} else {
scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&timeoutSeconds);
}
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, reqSenseLength, false, true );
req->setTimeout( timeoutSeconds * 1000 );
#if 0
if (req->completionAction == NULL) {
IOPanic("IOBasicSCSI::doAsyncReadWite: completionAction is NULL!");
}
#endif
/* Queue the request awaiting power and return. When power comes up,
* the request will be passed to standardAsyncReadWriteExecute.
*/
queueCommand(cx,kAsync,getReadWritePowerState()); /* queue and possibly wait for power */
return(kIOReturnSuccess);
}
IOReturn
IOBasicSCSI::standardAsyncReadWriteExecute(struct context *cx)
{
return(cx->scsireq->execute());
}
IOReturn
IOBasicSCSI::standardSyncReadWrite(IOMemoryDescriptor *buffer,UInt32 block,UInt32 nblks)
{
struct context *cx;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt32 reqSenseLength;
UInt32 reqTimeoutSeconds;
UInt8 *cdb;
bool isWrite;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
cx->memory = buffer;
buffer->retain(); /* bump the retain count */
if (buffer->getDirection() == kIODirectionOut) {
isWrite = true;
} else {
isWrite = false;
}
/**
IOLog("%s[IOBasicSCSI]::standardSyncReadWrite; (%s) blk %ld nblks %ld\n",
getName(),(isWrite ? "write" : "read"),block,nblks);
**/
bzero(&scsiCDB,sizeof(scsiCDB));
req = cx->scsireq;
req->setPointers(buffer,(nblks * getBlockSize()),isWrite);
cdb = (UInt8 *)&scsiCDB.cdb;
/* Allow a subclass to override the creation of the cdb and specify
* other parameters for the operation.
*/
if (isWrite) {
scsiCDB.cdbFlags |= createWriteCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&reqTimeoutSeconds);
} else {
scsiCDB.cdbFlags |= createReadCdb(cdb,&scsiCDB.cdbLength,
block,nblks,
&reqSenseLength,
&reqTimeoutSeconds);
}
req->setCDB(&scsiCDB);
req->setPointers(cx->senseDataDesc,reqSenseLength,false,true);
req->setTimeout(reqTimeoutSeconds * 1000);
queueCommand(cx,kSync,getReadWritePowerState()); /* queue the operation, sleep awaiting power */
result = simpleSynchIO(cx); /* issue a simple command */
deleteContext(cx);
return(result);
}
char *
IOBasicSCSI::stringFromState(stateValue state)
{
static char *stateNames[] = {
"kNone",
"kAsyncReadWrite",
"kSimpleSynchIO",
"kHandlingUnitAttention",
"kDoneHandlingUnitAttention"
};
if (state < 0 || state > kMaxValidState) {
return("invalid");
}
return(stateNames[state]);
}