IOSCSIParallelController.cpp [plain text]
#include <libkern/c++/OSMetaClass.h>
#include <IOKit/IOSyncer.h>
#undef super
#define super IOService
#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
#include <IOKit/storage/IOStorageProtocolCharacteristics.h>
#include "IOSCSIParallelController.h"
#include "IOSCSIParallelDevice.h"
#include "IOSCSIParallelCommand.h"
OSDefineMetaClass( IOSCSIParallelController, IOService )
OSDefineAbstractStructors( IOSCSIParallelController, IOService );
#define round(x,y) (((int)(x) + (y) - 1) & ~((y)-1))
SInt32 IOSCSIParallelController::sDomainCount = 1;
bool IOSCSIParallelController::start( IOService *forProvider )
{
UInt32 domainID;
provider = forProvider;
if ( provider->open( this ) != true )
{
return false;
}
if ( createWorkLoop() != true )
{
return false;
}
if ( configureController() == false )
{
provider->close( this );
return false;
}
initQueues();
domainID = ( OSIncrementAtomic ( &sDomainCount ) << 16 );
setProperty ( kIOPropertySCSIDomainIdentifierKey, ( UInt64 ) domainID, 32 );
setProperty ( kIOPropertySCSIInitiatorIdentifierKey, controllerInfo.initiatorId, 64 );
if ( scanSCSIBus() == false )
{
provider->close( this );
return false;
}
registerService ( );
return true;
}
bool IOSCSIParallelController::scanSCSIBus()
{
SCSITargetLun targetLun;
UInt32 i;
bzero( &targetLun, sizeof( targetLun) );
for ( i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
targetLun.target = i;
probeTarget( targetLun );
}
return true;
}
bool IOSCSIParallelController::probeTarget( SCSITargetLun targetLun )
{
IOSCSIParallelDevice *device;
UInt32 i;
if ( targetLun.target == controllerInfo.initiatorId )
{
return false;
}
if ( initTarget( targetLun ) == false )
{
releaseTarget( targetLun );
return false;
}
for ( i=0; i < controllerInfo.maxLunsSupported; i++ )
{
targetLun.lun = i;
device = createDevice();
if ( device == 0 )
{
break;
}
if ( device->init( this, targetLun ) == false )
{
releaseDevice( device );
break;
}
if ( initDevice( device ) == false )
{
releaseDevice( device );
continue;
}
if ( device->probeTargetLun() != kIOReturnSuccess )
{
releaseDevice( device );
if ( i == 0 ) break;
}
}
if ( i == 0 )
{
releaseTarget( targetLun );
return false;
}
queue_iterate( &targets[targetLun.target].deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
device->setupTarget();
device->attach( this );
device->registerService();
}
return true;
}
bool IOSCSIParallelController::initTargetGated( SCSITargetLun *targetLun )
{
return initTarget( *targetLun );
}
bool IOSCSIParallelController::initTarget( SCSITargetLun targetLun )
{
SCSITarget *target;
UInt32 number;
if ( getWorkLoop()->inGate() == false )
{
return controllerGate->runAction(
OSMemberFunctionCast(IOCommandGate::Action, this, &IOSCSIParallelController::initTargetGated),
(void *)&targetLun );
}
target = &targets[targetLun.target];
target->clientSem = IORWLockAlloc();
target->targetSem = IORWLockAlloc();
if( (target->targetSem == 0) || (target->clientSem == 0))
{
return false;
}
target->commandLimitSave = target->commandLimit = 1;
target->targetParmsCurrent.transferWidth = 1;
if ( controllerInfo.targetPrivateDataSize != 0 )
{
target->targetPrivateData = IOMallocContiguous( controllerInfo.targetPrivateDataSize, 16, 0 );
if ( target->targetPrivateData == 0 )
{
return false;
}
}
if ( controllerInfo.tagAllocationMethod == kTagAllocationPerTarget )
{
target->tagArray = (UInt32 *)IOMalloc( tagArraySize );
if ( target->tagArray == 0 )
{
return false;
}
bzero( target->tagArray, tagArraySize );
}
number = 0;
target->regObjTransferPeriod = OSNumber::withNumber( number, 32 );
if ( target->regObjTransferPeriod == 0 )
{
return false;
}
number = 0;
target->regObjTransferOffset = OSNumber::withNumber( number, 32 );
if ( target->regObjTransferOffset == 0 )
{
return false;
}
number = 1;
target->regObjTransferWidth = OSNumber::withNumber( number, 32 );
if ( target->regObjTransferWidth == 0 )
{
return false;
}
number = 0;
target->regObjTransferOptions = OSNumber::withNumber( number, 32 );
if ( target->regObjTransferOptions == 0 )
{
return false;
}
number = 0;
target->regObjCmdQueue = OSNumber::withNumber( number, 32 );
if ( target->regObjCmdQueue == 0 )
{
return false;
}
target->targetAllocated = allocateTarget( targetLun );
return target->targetAllocated;
}
void IOSCSIParallelController::releaseTargetGated( SCSITargetLun *targetLun )
{
releaseTarget( *targetLun );
}
void IOSCSIParallelController::releaseTarget( SCSITargetLun targetLun )
{
SCSITarget *target;
if ( getWorkLoop()->inGate() == false )
{
controllerGate->runAction(
OSMemberFunctionCast(IOCommandGate::Action, this, &IOSCSIParallelController::releaseTargetGated),
(void *)&targetLun );
return;
}
target = &targets[targetLun.target];
if ( queue_empty( &target->deviceList ) != true )
{
IOLog("IOSCSIParallelController()::Target %d deleted with lun(s) active!\n\r",
targetLun.target );
}
if ( target->targetAllocated == true )
{
deallocateTarget( targetLun );
target->targetAllocated = false;
}
if ( target->tagArray != 0 )
{
IOFree( target->tagArray, tagArraySize );
target->tagArray = 0;
}
if ( target->targetPrivateData != 0 )
{
IOFreeContiguous( target->targetPrivateData, controllerInfo.targetPrivateDataSize );
target->targetPrivateData = 0;
}
if ( target->clientSem != 0 )
{
IORWLockFree( target->clientSem );
}
if ( target->targetSem != 0 )
{
IORWLockFree( target->targetSem );
}
if ( target->regObjTransferPeriod != 0 )
{
target->regObjTransferPeriod->release();
target->regObjTransferPeriod = 0;
}
if ( target->regObjTransferOffset != 0 )
{
target->regObjTransferOffset->release();
target->regObjTransferOffset = 0;
}
if ( target->regObjTransferWidth != 0 )
{
target->regObjTransferWidth->release();
target->regObjTransferWidth = 0;
}
if ( target->regObjCmdQueue != 0 )
{
target->regObjCmdQueue->release();
target->regObjCmdQueue = 0;
}
}
bool IOSCSIParallelController::initDeviceGated( IOSCSIParallelDevice *device )
{
return initDevice( device );
}
bool IOSCSIParallelController::initDevice( IOSCSIParallelDevice *device )
{
if ( getWorkLoop()->inGate() == false )
{
return controllerGate->runAction(
OSMemberFunctionCast(IOCommandGate::Action, this, &IOSCSIParallelController::initDeviceGated),
(void *)device );
}
addDevice( device );
device->lunAllocated = allocateLun( device->targetLun );
return device->lunAllocated;
}
void IOSCSIParallelController::releaseDeviceGated( IOSCSIParallelDevice *device )
{
releaseDevice( device );
return;
}
void IOSCSIParallelController::releaseDevice( IOSCSIParallelDevice *device )
{
if ( getWorkLoop()->inGate() == false )
{
controllerGate->runAction(
OSMemberFunctionCast(IOCommandGate::Action, this, &IOSCSIParallelController::releaseDeviceGated),
(void *)device );
return;
}
deleteDevice( device );
if ( device->lunAllocated == true )
{
deallocateLun( device->targetLun );
}
device->release();
}
void IOSCSIParallelController::addDevice( IOSCSIParallelDevice *forDevice )
{
UInt32 targetID;
targetID = forDevice->targetLun.target;
forDevice->target = &targets[targetID];
queue_enter( &targets[targetID].deviceList, forDevice, IOSCSIParallelDevice *, nextDevice );
}
void IOSCSIParallelController::deleteDevice( IOSCSIParallelDevice *forDevice )
{
queue_head_t *deviceList;
IOSCSIParallelDevice *device;
UInt32 targetID;
targetID = forDevice->targetLun.target;
deviceList = &targets[targetID].deviceList;
queue_iterate( deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
if ( device == forDevice )
{
queue_remove( &targets[targetID].deviceList, device, IOSCSIParallelDevice *, nextDevice );
break;
}
}
}
bool IOSCSIParallelController::allocateTarget( SCSITargetLun targetLun )
{
return true;
}
void IOSCSIParallelController::deallocateTarget( SCSITargetLun targetLun )
{
}
bool IOSCSIParallelController::allocateLun( SCSITargetLun targetLun )
{
return true;
}
void IOSCSIParallelController::deallocateLun( SCSITargetLun targetLun )
{
}
void *IOSCSIParallelController::getTargetData( SCSITargetLun targetLun )
{
return targets[targetLun.target].targetPrivateData;
}
void *IOSCSIParallelController::getLunData( SCSITargetLun targetLun )
{
queue_head_t *deviceList;
IOSCSIParallelDevice *device;
deviceList = &targets[targetLun.target].deviceList;
queue_iterate( deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
if ( device->targetLun.lun == targetLun.lun )
{
return device->devicePrivateData;
}
}
return 0;
}
IOSCSIParallelDevice *IOSCSIParallelController::createDevice()
{
return new IOSCSIParallelDevice;
}
void IOSCSIParallelController::initQueues()
{
UInt32 i;
for ( i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
queue_init( &targets[i].deviceList );
}
resetCmd = allocCommand( 0 );
resetCmd->cmdType = kSCSICommandBusReset;
timer( timerEvent );
}
void IOSCSIParallelController::reset()
{
IOSCSIParallelDevice *device;
UInt32 i;
if ( busResetState != kStateIssue )
{
return;
}
busResetState = kStateActive;
for (i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
queue_iterate( &targets[i].deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
if ( device->client != 0 )
{
device->client->message( kSCSIClientMsgBusReset, device );
}
}
}
resetCommand( resetCmd );
}
bool IOSCSIParallelController::checkBusReset()
{
if ( busResetState == kStateIdle )
{
return false;
}
if ( busResetState == kStateIssue )
{
reset();
}
return true;
}
void IOSCSIParallelController::resetOccurred()
{
UInt32 i;
IOSCSIParallelDevice *device;
SCSITarget *target;
SCSIClientMessage clientMsg;
for (i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
target = &targets[i];
target->commandLimit = target->commandLimitSave;
target->reqSenseCount = 0;
target->reqSenseState = kStateIdle;
target->negotiateState = kStateIssue;
target->targetParmsCurrent.transferPeriodpS = 0;
target->targetParmsCurrent.transferOffset = 0;
target->targetParmsCurrent.transferWidth = 1;
noDisconnectCmd = 0;
clientMsg = ( busResetState != kStateActive ) ? kSCSIClientMsgBusReset : kSCSIClientMsgNone;
queue_iterate( &target->deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
device->resetOccurred( clientMsg );
}
}
resetTimer = (kSCSIResetIntervalmS / kSCSITimerIntervalmS + 1);
}
void IOSCSIParallelController::timer( IOTimerEventSource * )
{
UInt32 i;
IOSCSIParallelDevice *device;
if ( disableTimer )
{
if ( !--disableTimer )
{
disableTimeoutOccurred();
}
}
if ( resetTimer )
{
if ( !--resetTimer )
{
for (i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
queue_iterate( &targets[i].deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
device->resetComplete();
}
}
}
}
else
{
for (i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
queue_iterate( &targets[i].deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
device->timer();
}
}
}
timerEvent->setTimeoutMS(kSCSITimerIntervalmS);
}
void IOSCSIParallelController::completeCommand( IOSCSIParallelCommand *scsiCmd )
{
switch ( scsiCmd->cmdType )
{
case kSCSICommandBusReset:
resetOccurred();
busResetState = kStateIdle;
break;
default:
;
}
}
bool IOSCSIParallelController::createWorkLoop()
{
workLoop = getWorkLoop();
if ( workLoop == 0 )
{
workLoop = IOWorkLoop::workLoop();
if ( workLoop == 0 )
{
return false;
}
}
timerEvent = IOTimerEventSource::timerEventSource( this, OSMemberFunctionCast(IOTimerEventSource::Action, this, &IOSCSIParallelController::timer ) );
if ( timerEvent == 0 )
{
return false;
}
if ( workLoop->addEventSource( timerEvent ) != kIOReturnSuccess )
{
return false;
}
dispatchEvent = IOInterruptEventSource::interruptEventSource(
this,
OSMemberFunctionCast ( IOInterruptEventAction, this, &IOSCSIParallelController::dispatch ),
0 );
if ( dispatchEvent == 0 )
{
return false;
}
if ( workLoop->addEventSource( dispatchEvent ) != kIOReturnSuccess )
{
return false;
}
controllerGate = IOCommandGate::commandGate( this );
if ( controllerGate == 0 )
{
return false;
}
if ( workLoop->addEventSource( controllerGate ) != kIOReturnSuccess )
{
return false;
}
return true;
}
IOSCSIParallelCommand *IOSCSIParallelController::findCommandWithNexus( SCSITargetLun targetLun, UInt32 tagValue )
{
IOSCSIParallelDevice *device;
device = findDeviceWithTargetLun( targetLun );
if ( device == 0 )
{
return 0;
}
return device->findCommandWithNexus( tagValue );
}
IOSCSIParallelDevice *IOSCSIParallelController::findDeviceWithTargetLun( SCSITargetLun targetLun )
{
IOSCSIParallelDevice *device;
if ( targetLun.target > controllerInfo.maxTargetsSupported || targetLun.lun > controllerInfo.maxLunsSupported )
{
return 0;
}
queue_iterate( &targets[targetLun.target].deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
if ( device->targetLun.lun == targetLun.lun )
{
return device;
}
}
return 0;
}
bool IOSCSIParallelController::configureController()
{
UInt32 targetsSize;
if ( configure( provider, &controllerInfo ) == false )
{
return false;
}
controllerInfo.commandPrivateDataSize = round( controllerInfo.commandPrivateDataSize, 16 );
if ( controllerInfo.maxCommandsPerController == 0 ) controllerInfo.maxCommandsPerController = (UInt32) -1;
if ( controllerInfo.maxCommandsPerTarget == 0 ) controllerInfo.maxCommandsPerTarget = (UInt32) -1;
if ( controllerInfo.maxCommandsPerLun == 0 ) controllerInfo.maxCommandsPerLun = (UInt32) -1;
targetsSize = controllerInfo.maxTargetsSupported * sizeof(SCSITarget);
targets = (SCSITarget *)IOMalloc( targetsSize );
bzero( targets, targetsSize );
commandLimit = commandLimitSave = controllerInfo.maxCommandsPerController;
tagArraySize = (controllerInfo.maxTags / 32 + ((controllerInfo.maxTags % 32) ? 1 : 0)) * sizeof(UInt32);
if ( controllerInfo.tagAllocationMethod == kTagAllocationPerController )
{
tagArray = (UInt32 *)IOMalloc( tagArraySize );
bzero( tagArray, tagArraySize );
}
return true;
}
void IOSCSIParallelController::setCommandLimit( UInt32 newCommandLimit )
{
if ( newCommandLimit == 0 ) controllerInfo.maxCommandsPerController = (UInt32) -1;
commandLimit = commandLimitSave = controllerInfo.maxCommandsPerController;
}
IOWorkLoop *IOSCSIParallelController::getWorkLoop() const
{
return workLoop;
}
void IOSCSIParallelController::disableCommands( UInt32 disableTimeoutmS )
{
commandDisable = true;
disableTimer = ( disableTimeoutmS != 0 ) ? (disableTimeoutmS / kSCSITimerIntervalmS + 1) : 0;
}
void IOSCSIParallelController::disableCommands()
{
UInt32 disableTimeout;
commandDisable = true;
disableTimeout = kSCSIDisableTimeoutmS;
if ( noDisconnectCmd != 0 )
{
disableTimeout = noDisconnectCmd->getTimeout();
if ( disableTimeout != 0 ) disableTimeout += kSCSIDisableTimeoutmS;
}
disableTimer = ( disableTimeout != 0 ) ? (disableTimeout / kSCSITimerIntervalmS + 1) : 0;
}
void IOSCSIParallelController::disableTimeoutOccurred()
{
busResetState = kStateIssue;
dispatchRequest();
}
void IOSCSIParallelController::rescheduleCommand( IOSCSIParallelCommand *forSCSICmd )
{
forSCSICmd->getDevice(kIOSCSIParallelDevice)->rescheduleCommand( forSCSICmd );
}
void IOSCSIParallelController::enableCommands()
{
commandDisable = false;
disableTimer = 0;
dispatchRequest();
}
void IOSCSIParallelController::dispatchRequest()
{
dispatchEvent->interruptOccurred(0, 0, 0);
}
void IOSCSIParallelController::dispatch()
{
SCSITarget *target;
IOSCSIParallelDevice *device;
UInt32 dispatchAction;
UInt32 lunsActive = 0;
UInt32 i;
if ( !targets || checkBusReset() )
{
goto dispatch_Exit;
}
for ( i = 0; i < controllerInfo.maxTargetsSupported; i++ )
{
target = &targets[i];
if ( target->state == kStateActive )
{
lunsActive = 0;
queue_iterate( &target->deviceList, device, IOSCSIParallelDevice *, nextDevice )
{
if ( device->dispatch( &dispatchAction ) == true )
{
lunsActive++;
}
switch ( dispatchAction )
{
case kDispatchNextLun:
;
case kDispatchNextTarget:
break;
case kDispatchStop:
goto dispatch_Exit;
}
}
if ( lunsActive == 0 )
{
target->state = kStateIdle;
}
}
}
dispatch_Exit:
;
}
IOSCSIParallelCommand *IOSCSIParallelController::allocCommand(UInt32 clientDataSize )
{
IOSCSIParallelCommand *cmd;
UInt32 size;
size = controllerInfo.commandPrivateDataSize + round(clientDataSize, 16);
cmd = new IOSCSIParallelCommand;
if ( !cmd )
{
return 0;
}
cmd->init();
if ( size )
{
cmd->dataArea = (void *)IOMallocContiguous( (vm_size_t)size, 16, 0 );
if ( !cmd->dataArea )
{
cmd->release();
return 0;
}
bzero( cmd->dataArea, size );
cmd->dataSize = size;
if ( controllerInfo.commandPrivateDataSize )
{
cmd->commandPrivateData = cmd->dataArea;
}
if ( clientDataSize )
{
cmd->clientData = (void *)((UInt8 *)cmd->dataArea + controllerInfo.commandPrivateDataSize);
}
}
cmd->controller = this;
return cmd;
}
void IOSCSIParallelController::free()
{
UInt32 targetsSize;
UInt32 i;
if ( controllerGate != 0 )
{
workLoop->removeEventSource( controllerGate );
controllerGate->release();
}
if ( timerEvent != 0 ) timerEvent->release();
if ( dispatchEvent != 0 ) dispatchEvent->release();
if ( resetCmd != 0 ) resetCmd->release();
if ( workLoop != 0 ) workLoop->release();
if ( targets != 0 )
{
for ( i=0; i < controllerInfo.maxTargetsSupported; i++ )
{
if ( targets[i].targetPrivateData != 0 )
{
IOFreeContiguous( targets[i].targetPrivateData, controllerInfo.targetPrivateDataSize );
}
}
targetsSize = controllerInfo.maxTargetsSupported * sizeof(SCSITarget);
IOFree( targets, targetsSize );
}
if ( tagArray != 0 ) IOFree( tagArray, tagArraySize );
super::free();
}
void IOSCSIParallelCommand::free()
{
if ( dataArea )
{
IOFreeContiguous( dataArea, dataSize );
}
OSObject::free();
}