#include <IOKit/IOLib.h>
#include <IOKit/IOReturn.h>
#include <IOKit/scsi/IOSCSIDeviceInterface.h>
#include <IOKit/storage/scsi/IOSCSIHDDrive.h>
#include <IOKit/storage/scsi/IOSCSIHDDriveNub.h>
#define super IOBasicSCSI
OSDefineMetaClassAndStructors(IOSCSIHDDrive,IOBasicSCSI)
IOReturn
IOSCSIHDDrive::allocateFormatBuffer(UInt8 **buf,UInt32 *len)
{
*buf = 0;
*len = 0;
return(kIOReturnSuccess);
}
UInt8
IOSCSIHDDrive::composeFormatBuffer(UInt8 * ,UInt32 )
{
return(0);
}
OSDictionary *
IOSCSIHDDrive::constructDeviceProperties(void)
{
OSDictionary *propTable;
OSData *prop;
char *typeString;
propTable = OSDictionary::withCapacity(6);
if (propTable) {
prop = OSData::withBytes((void *)(&_vendor),strlen(_vendor));
if (prop) {
propTable->setObject("vendor", prop);
}
prop = OSData::withBytes((void *)(&_product),strlen(_product));
if (prop) {
propTable->setObject("product", prop);
}
prop = OSData::withBytes((void *)(&_rev),strlen(_rev));
if (prop) {
propTable->setObject("revision", prop);
}
typeString = (char *)getDeviceTypeName();
prop = OSData::withBytes((void *)(typeString),strlen(typeString));
if (prop) {
propTable->setObject("device-type", prop);
}
#ifdef xxx
prop = OSData::withBytes((void *)(&_removable),sizeof(bool));
if (prop) {
propTable->setObject("removable", prop);
}
prop = OSData::withBytes((void *)(&_ejectable),sizeof(bool));
if (prop) {
propTable->setObject("ejectable", prop);
}
#endif //xxx
}
return(propTable);
}
UInt32
IOSCSIHDDrive::createFormatCdb(UInt64 ,
UInt8 *cdb,UInt32 *cdbLength,
UInt8 buf[],UInt32 bufLen,
UInt32 *maxAutoSenseLength,UInt32 *timeoutSeconds)
{
struct IOFormatcdb *c;
UInt8 formatControls;
c = (struct IOFormatcdb *)cdb;
c->opcode = kIOSCSICommandFormatUnit;
c->lunbits = 0;
c->vendor = 0;
c->interleave_msb = 0;
c->interleave_lsb = 0;
c->ctlbyte = 0;
*cdbLength = 6;
if (buf != NULL) {
formatControls = composeFormatBuffer(buf,bufLen);
c->lunbits |= (formatControls | 0x10);
}
*maxAutoSenseLength = sizeof(SCSISenseData);
*timeoutSeconds = 0;
return(0);
}
IOService *
IOSCSIHDDrive::createNub(void)
{
IOService *nub;
nub = instantiateNub();
if (nub == NULL) {
IOLog("%s[IOSCSIHDDrive]::createNub; nub didn't instantiate\n",getName());
return(NULL);
}
nub->init();
if (!nub->attach(this)) {
IOPanic("IOSCSIHDDrive::createNub; couldn't attach IOSCSIHDDriveNub");
}
nub->registerService();
return(nub);
}
void
IOSCSIHDDrive::deleteFormatBuffer(UInt8 * , UInt32 )
{
}
bool
IOSCSIHDDrive::deviceTypeMatches(UInt8 inqBuf[],UInt32 inqLen,SInt32 * )
{
if ((_inqBuf[0] & 0x1f) == kIOSCSIDeviceTypeDirectAccess) {
return(true);
} else {
return(false);
}
}
IOReturn
IOSCSIHDDrive::doAsyncReadWrite(IOMemoryDescriptor *buffer,
UInt32 block,UInt32 nblks,
IOStorageCompletion completion)
{
return(standardAsyncReadWrite(buffer,block,nblks,completion));
}
IOReturn
IOSCSIHDDrive::doEjectMedia(void)
{
return(doStartStop(false,true,IOStartStopcdb::P_NOCHANGE));
}
IOReturn
IOSCSIHDDrive::doFormatMedia(UInt64 byteCapacity)
{
return(standardFormatMedia(byteCapacity));
}
UInt32
IOSCSIHDDrive::doGetFormatCapacities(UInt64 * capacities,
UInt32 capacitiesMaxCount) const
{
if ((capacities != NULL) && (capacitiesMaxCount > 0)) {
*capacities = _blockSize * (_maxBlock + 1);
return(1);
} else {
return(0);
}
}
IOReturn
IOSCSIHDDrive::doLockUnlockMedia(bool doLock)
{
struct context *cx;
struct IOPrevAllowcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
c = (struct IOPrevAllowcdb *)&scsiCDB.cdb;
c->opcode = kIOSCSICommandPreventAllow;
c->lunbits = 0;
c->reserved1 = 0;
c->reserved2 = 0;
if (doLock) {
c->prevent = 0x01;
} else {
c->prevent = 0x00;
}
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
req->setCDB( &scsiCDB );
cx->memory = 0;
req->setPointers( cx->memory, 0, false );
queueCommand(cx,kSync,getLockUnlockMediaPowerState());
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSIHDDrive::doStart(void)
{
return(doStartStop(true,false,IOStartStopcdb::P_ACTIVE));
}
IOReturn
IOSCSIHDDrive::doStop(void)
{
return(doStartStop(false,false,IOStartStopcdb::P_NOCHANGE));
}
IOReturn
IOSCSIHDDrive::doStartStop(bool start,bool loadEject,UInt8 powerCondition)
{
struct context *cx;
struct IOStartStopcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
UInt32 powerLevel;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
powerLevel = getStopPowerState();
req = cx->scsireq;
bzero( &scsiCDB, sizeof(SCSICDBInfo) );
c = (struct IOStartStopcdb *)&scsiCDB.cdb;
c->opcode = kIOSCSICommandStartStopUnit;
c->lunImmed = 0;
c->reserved1 = 0;
c->reserved2 = 0;
c->controls = powerCondition;
c->controls = 0;
if (loadEject) {
c->controls |= IOStartStopcdb::C_LOEJ;
powerLevel = getEjectPowerState();
};
if (start) {
c->controls |= IOStartStopcdb::C_SPINUP;
powerLevel = getStartPowerState();
}
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
req->setCDB( &scsiCDB );
req->setTimeout( 30000 );
cx->memory = 0;
req->setPointers( cx->memory, 0, false );
queueCommand(cx,kSync,powerLevel);
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSIHDDrive::doSynchronizeCache(void)
{
return(standardSynchronizeCache());
}
IOReturn
IOSCSIHDDrive::doSyncReadWrite(IOMemoryDescriptor *buffer,UInt32 block,UInt32 nblks)
{
return(standardSyncReadWrite(buffer,block,nblks));
}
const char *
IOSCSIHDDrive::getDeviceTypeName(void)
{
return(kIOBlockStorageDeviceTypeGeneric);
}
UInt32
IOSCSIHDDrive::getEjectPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getExecuteCDBPowerState(void)
{
return(kAllOn);
}
UInt32
IOSCSIHDDrive::getFormatMediaPowerState(void)
{
return(kAllOn);
}
UInt32
IOSCSIHDDrive::getInitialPowerState(void)
{
return(kAllOn);
}
UInt32
IOSCSIHDDrive::getInquiryPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getLockUnlockMediaPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getReadCapacityPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getReadWritePowerState(void)
{
return(kAllOn);
}
UInt32
IOSCSIHDDrive::getReportWriteProtectionPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getStartPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getStopPowerState(void)
{
return(kElectronicsOn);
}
UInt32
IOSCSIHDDrive::getSynchronizeCachePowerState(void)
{
return(kAllOn);
}
UInt32
IOSCSIHDDrive::getTestUnitReadyPowerState(void)
{
return(kElectronicsOn);
}
bool
IOSCSIHDDrive::init(OSDictionary * properties)
{
_mediaPresent = false;
_startStopDisabled = false;
return(super::init(properties));
}
IOService *
IOSCSIHDDrive::instantiateNub(void)
{
IOService *nub;
nub = new IOSCSIHDDriveNub;
return(nub);
}
bool
IOSCSIHDDrive::powerTickle(UInt32 desiredState)
{
return(activityTickle(kIOPMSuperclassPolicy1,desiredState));
}
IOReturn
IOSCSIHDDrive::reportMediaState(bool *mediaPresent,bool *changed)
{
struct context *cx;
struct IOTURcdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
SCSIResults scsiResults;
IOReturn result;
UInt8 status;
UInt8 senseKey;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
c = (struct IOTURcdb *)&scsiCDB.cdb;
c->opcode = kIOSCSICommandTestUnitReady;
c->lunbits = 0;
c->reserved1 = 0;
c->reserved2 = 0;
c->reserved3 = 0;
c->ctlbyte = 0;
scsiCDB.cdbLength = 6;
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, 255, false, true );
req->setTimeout( 5000 );
cx->memory = 0;
req->setPointers( cx->memory, 0, false );
queueCommand(cx,kSync,getTestUnitReadyPowerState());
result = simpleSynchIO(cx);
req->getResults( &scsiResults );
status = scsiResults.scsiStatus;
if (result == kIOReturnSuccess) {
*mediaPresent = true;
*changed = (*mediaPresent != _mediaPresent);
_mediaPresent = true;
result = kIOReturnSuccess;
} else {
if ( scsiResults.requestSenseDone == true ) {
senseKey = cx->senseData->senseKey;
if (senseKey == 0x02) {
*mediaPresent = false;
*changed = (*mediaPresent != _mediaPresent);
_mediaPresent = false;
result = kIOReturnSuccess;
} else {
*mediaPresent = false;
*changed = (*mediaPresent != _mediaPresent);
_mediaPresent = false;
result = kIOReturnIOError;
}
} else {
*mediaPresent = _mediaPresent;
*changed = false;
result = kIOReturnSuccess;
}
}
if (*changed && *mediaPresent) {
_readCapDone = false;
_blockSize = 0;
_maxBlock = 0;
}
deleteContext(cx);
#ifndef DISKPM
if (*changed && *mediaPresent)
doStart();
#endif
return(result);
}
IOReturn
IOSCSIHDDrive::restoreElectronicsState(void)
{
return(kIOReturnSuccess);
}
void
IOSCSIHDDrive::RWCompletion(struct context *cx)
{
SCSIResults scsiResults;
cx->scsireq->getResults( &scsiResults );
IOStorage::complete(cx->completion,
scsiResults.returnCode,
scsiResults.bytesTransferred);
dequeueCommands();
}
IOReturn
IOSCSIHDDrive::saveElectronicsState(void)
{
return(kIOReturnSuccess);
}
static IOPMPowerState ourPowerStates[kNumberOfPowerStates] = {
{1,IOPMNotAttainable,0,0,0,0,0,0,0,0,0,0},
{1,0,0,IOPMPowerOn,0,0,0,0,0,0,0,0},
{1,0,0,IOPMPowerOn,0,0,0,0,0,0,0,0}
};
IOReturn
IOSCSIHDDrive::standardFormatMedia(UInt64 byteCapacity)
{
struct context *cx;
UInt8 *fmtbuf;
IOReturn result;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
UInt32 transferLength;
UInt32 senseLength;
UInt32 timeoutSeconds;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
result = allocateFormatBuffer(&fmtbuf,&transferLength);
if (result != kIOReturnSuccess) {
return(result);
}
bzero( &scsiCDB, sizeof(scsiCDB) );
scsiCDB.cdbFlags |= createFormatCdb(byteCapacity,(UInt8 *)&scsiCDB.cdb,&scsiCDB.cdbLength,
fmtbuf,transferLength,
&senseLength,
&timeoutSeconds);
req->setCDB( &scsiCDB );
req->setPointers( cx->senseDataDesc, senseLength, false, true );
req->setTimeout( timeoutSeconds * 1000 );
if ((fmtbuf != NULL) && (transferLength != 0)) {
cx->memory = IOMemoryDescriptor::withAddress((void *)fmtbuf,
transferLength,
kIODirectionOut);
}
req->setPointers( cx->memory, transferLength, true );
queueCommand(cx,kSync,getFormatMediaPowerState());
result = simpleSynchIO(cx);
deleteFormatBuffer(fmtbuf,transferLength);
deleteContext(cx);
return(result);
}
IOReturn
IOSCSIHDDrive::standardSynchronizeCache(void)
{
struct context *cx;
struct IOSyncCachecdb *c;
IOSCSICommand *req;
SCSICDBInfo scsiCDB;
IOReturn result;
cx = allocateContext();
if (cx == NULL) {
return(kIOReturnNoMemory);
}
req = cx->scsireq;
bzero( &scsiCDB, sizeof(scsiCDB) );
c = (struct IOSyncCachecdb *)&scsiCDB.cdb;
c->opcode = kIOSCSICommandSynchronizeCache;
c->lunbits = 0;
c->lba_3 = 0;
c->lba_2 = 0;
c->lba_1 = 0;
c->lba_0 = 0;
c->reserved = 0;
c->nblks_msb = 0;
c->nblks_lsb = 0;
c->ctlbyte = 0;
scsiCDB.cdbLength = 10;
req->setCDB( &scsiCDB );
cx->memory = 0;
req->setPointers( cx->memory, 0, false );
queueCommand(cx,kSync,getSynchronizeCachePowerState());
result = simpleSynchIO(cx);
deleteContext(cx);
return(result);
}
bool
IOSCSIHDDrive::start(IOService *provider)
{
IOService *nub;
if (!super::start(provider)) {
return(false);
}
PMinit();
_restoreState = false;
#ifdef notyet // don't register for PM yet till we handle queuing requests!
IOPMRegisterDevice(pm_vars->ourName,this); #endif
registerPowerDriver(this,ourPowerStates,kNumberOfPowerStates);
nub = createNub();
if (nub == NULL) {
return(false);
} else {
return(true);
}
}
unsigned long
IOSCSIHDDrive::maxCapabilityForDomainState(IOPMPowerFlags domainState)
{
if (domainState & IOPMPowerOn) {
return(kAllOn);
} else {
return(kAllOff);
}
}
unsigned long
IOSCSIHDDrive::powerStateForDomainState(IOPMPowerFlags domainState)
{
if (domainState & IOPMPowerOn) {
return(kAllOn);
} else {
return(kAllOff);
}
}
unsigned long
IOSCSIHDDrive::initialPowerStateForDomainState(IOPMPowerFlags domainState)
{
if (domainState & IOPMPowerOn) {
return(getInitialPowerState());
} else {
return(kAllOff);
}
}
IOReturn
IOSCSIHDDrive::setPowerState(unsigned long powerStateOrdinal,IOService *)
{
IOReturn result;
result = kIOReturnSuccess;
switch (powerStateOrdinal) {
case kElectronicsOn :
if (pm_vars->myCurrentState == kAllOn) {
if (!_startStopDisabled) {
result = doStop();
if (result != kIOReturnSuccess) {
_startStopDisabled = true;
result = kIOReturnSuccess;
}
}
}
break;
case kAllOn :
if (pm_vars->myCurrentState == kElectronicsOn) {
if (!_startStopDisabled) {
result = doStart();
if (result != kIOReturnSuccess) {
_startStopDisabled = true;
result = kIOReturnSuccess;
}
}
}
break;
default:
result = kIOReturnSuccess;
break;
}
return(result);
}
IOReturn
IOSCSIHDDrive::powerStateWillChangeTo(unsigned long,unsigned long stateOrdinal,IOService *)
{
if ((pm_vars->myCurrentState == kAllOff) &&
(stateOrdinal > kAllOff)) {
_restoreState = true;
}
if ((stateOrdinal == kAllOff) &&
(pm_vars->myCurrentState > kAllOff)) {
saveElectronicsState();
}
return(IOPMAckImplied);
}
IOReturn
IOSCSIHDDrive::powerStateDidChangeTo(unsigned long,unsigned long stateOrdinal,IOService*)
{
IOReturn result;
if (_restoreState) {
result = restoreElectronicsState();
_restoreState = false;
}
if (stateOrdinal > kAllOff) {
dequeueCommands();
}
return IOPMAckImplied;
}