IOSCSIMultimediaCommandsDevicePM.cpp [plain text]
#include <IOKit/scsi-commands/SCSITask.h>
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/RootDomain.h>
#include "IOSCSIMultimediaCommandsDevice.h"
#define DEBUG 0
#define DEBUG_ASSERT_COMPONENT_NAME_STRING "MMCPower"
#if DEBUG
#define SCSI_MMC_DEVICE_DEBUGGING_LEVEL 0
#endif
#include "IOSCSIArchitectureModelFamilyDebugging.h"
#if ( SCSI_MMC_DEVICE_DEBUGGING_LEVEL >= 1 )
#define PANIC_NOW(x) IOPanic x
#else
#define PANIC_NOW(x)
#endif
#if ( SCSI_MMC_DEVICE_DEBUGGING_LEVEL >= 2 )
#define ERROR_LOG(x) IOLog x
#else
#define ERROR_LOG(x)
#endif
#if ( SCSI_MMC_DEVICE_DEBUGGING_LEVEL >= 3 )
#define STATUS_LOG(x) IOLog x
#else
#define STATUS_LOG(x)
#endif
#define super IOSCSIPrimaryCommandsDevice
#define kPowerStatusBufferSize 8
#define kPowerStatusByte 5
static IOPMPowerState sPowerStates[kMMCNumPowerStates] =
{
{ kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ kIOPMPowerStateVersion1, 0, IOPMPowerOn, IOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0 },
{ kIOPMPowerStateVersion1, (IOPMDeviceUsable | kIOPMPreventIdleSleep), IOPMPowerOn, IOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0 },
{ kIOPMPowerStateVersion1, (IOPMDeviceUsable | kIOPMPreventIdleSleep), IOPMPowerOn, IOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0 },
{ kIOPMPowerStateVersion1, (IOPMDeviceUsable | IOPMMaxPerformance | kIOPMPreventIdleSleep), IOPMPowerOn, IOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0 }
};
enum
{
kMMCPowerConditionsActive = 0x01,
kMMCPowerConditionsIdle = 0x02,
kMMCPowerConditionsStandby = 0x03,
kMMCPowerConditionsSleep = 0x05
};
static IOReturn
IOSCSIMultimediaCommandsDevicePowerDownHandler ( void * target,
void * refCon,
UInt32 messageType,
IOService * provider,
void * messageArgument,
vm_size_t argSize );
static IOReturn
IOSCSIMultimediaCommandsDevicePowerDownHandler ( void * target,
void * refCon,
UInt32 messageType,
IOService * provider,
void * messageArgument,
vm_size_t argSize )
{
return ( ( IOSCSIMultimediaCommandsDevice * ) target )->PowerDownHandler (
refCon,
messageType,
provider,
messageArgument,
argSize );
}
IOReturn
IOSCSIMultimediaCommandsDevice::PowerDownHandler ( void * refCon,
UInt32 messageType,
IOService * provider,
void * messageArgument,
vm_size_t argSize )
{
IOReturn status = kIOReturnUnsupported;
SCSITaskIdentifier request = NULL;
require ( messageType == kIOMessageSystemWillPowerOff, ErrorExit );
request = GetSCSITask ( );
require_nonzero ( request, ErrorExit );
if ( fMediaPresent == false )
{
UInt8 trayState = 0;
if ( fCurrentPowerState > kMMCPowerStateSleep )
{
status = GetTrayState ( &trayState );
if ( ( ( status == kIOReturnSuccess ) && ( trayState == 1 ) ) ||
( status != kIOReturnSuccess ) )
{
if ( START_STOP_UNIT ( request, 0, 0, 1, 1, 0 ) == true )
{
( void ) SendCommand ( request, kTenSecondTimeoutInMS );
}
status = kIOReturnSuccess;
}
}
}
else
{
if ( fCurrentPowerState > kMMCPowerStateSleep )
{
if ( START_STOP_UNIT ( request, 1, 0, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
status = kIOReturnSuccess;
}
ReleaseSCSITask ( request );
ErrorExit:
return status;
}
UInt32
IOSCSIMultimediaCommandsDevice::GetInitialPowerState ( void )
{
return kMMCPowerStateActive;
}
UInt32
IOSCSIMultimediaCommandsDevice::GetNumberOfPowerStateTransitions ( void )
{
UInt32 numTransitions = 0;
bool state = false;
state = HandleGetUserClientExclusivityState ( );
if ( state == false )
{
numTransitions = kMMCPowerStateActive - kMMCPowerStateSleep;
}
return numTransitions;
}
void
IOSCSIMultimediaCommandsDevice::InitializePowerManagement (
IOService * provider )
{
fCurrentPowerState = kMMCPowerStateActive;
super::InitializePowerManagement ( provider );
registerPowerDriver ( this, sPowerStates, kMMCNumPowerStates );
fPowerDownNotifier = registerPrioritySleepWakeInterest (
( IOServiceInterestHandler ) IOSCSIMultimediaCommandsDevicePowerDownHandler,
this );
changePowerStateTo ( kMMCPowerStateSleep );
}
void
IOSCSIMultimediaCommandsDevice::HandleCheckPowerState ( void )
{
if ( IsDeviceAccessEnabled ( ) )
{
super::HandleCheckPowerState ( kMMCPowerStateActive );
}
}
void
IOSCSIMultimediaCommandsDevice::TicklePowerManager ( void )
{
( void ) super::TicklePowerManager ( kMMCPowerStateActive );
}
void
IOSCSIMultimediaCommandsDevice::HandlePowerChange ( void )
{
SCSIServiceResponse serviceResponse;
SCSITaskIdentifier request = NULL;
UInt32 features;
STATUS_LOG ( ( "IOSCSIMultimediaCommandsDevice::HandlePowerChange called\n" ) );
request = GetSCSITask ( );
while ( ( fProposedPowerState != fCurrentPowerState ) && ( isInactive ( ) == false ) )
{
STATUS_LOG ( ( "fProposedPowerState = %ld, fCurrentPowerState = %ld\n",
fProposedPowerState, fCurrentPowerState ) );
if ( ( fCurrentPowerState <= kMMCPowerStateSleep ) &&
( fProposedPowerState > kMMCPowerStateSleep ) )
{
STATUS_LOG ( ( "We think we're in sleep\n" ) );
if ( TEST_UNIT_READY ( request, 0 ) == true )
{
( void ) SendCommand ( request, kOneSecondTimeoutInMS );
}
STATUS_LOG ( ( "calling ClearPowerOnReset\n" ) );
if ( ClearPowerOnReset ( ) == false )
break;
STATUS_LOG ( ( "calling ClearNotReadyStatus\n" ) );
if ( ClearNotReadyStatus ( ) == false )
break;
if ( fMediaIsRemovable == true )
{
if ( fMediaPresent == true )
{
bool mediaPresent = false;
for ( UInt32 index = 0; index < 100; index++ )
{
STATUS_LOG ( ( "Calling CheckMediaPresence\n" ) );
mediaPresent = CheckMediaPresence ( );
if ( mediaPresent )
break;
IOSleep ( 200 );
}
if ( mediaPresent != fMediaPresent )
{
PANIC_NOW ( ( "Unexpected Media Removal when waking up from sleep\n" ) );
}
else
{
STATUS_LOG ( ( "Calling PREVENT_ALLOW_MEDIUM_REMOVAL\n" ) );
if ( PREVENT_ALLOW_MEDIUM_REMOVAL ( request, kMediaStateLocked, 0 ) == true )
{
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS );
}
}
}
else
{
STATUS_LOG ( ( "Calling EnablePolling\n" ) );
fPollingMode = kPollingMode_NewMedia;
EnablePolling ( );
}
}
}
switch ( fProposedPowerState )
{
case kMMCPowerStateSystemSleep:
STATUS_LOG ( ( "case kMMCPowerStateSystemSleep\n" ) );
if ( fCurrentPowerState == kMMCPowerStateSleep )
{
STATUS_LOG ( ( "We are already asleep\n" ) );
if ( fMediaPresent == true )
{
STATUS_LOG ( ( "Media is present, bailing out\n" ) );
fCurrentPowerState = kMMCPowerStateSystemSleep;
break;
}
else if ( fLowPowerPollingEnabled )
{
STATUS_LOG ( ( "Low power polling is enabled, let's disable it now.\n" ) );
features = fLowPowerPollingEnabled = false;
HandleProtocolServiceFeature ( kSCSIProtocolFeature_ProtocolSpecificPolling, ( void * ) &features );
fCurrentPowerState = kMMCPowerStateSystemSleep;
STATUS_LOG ( ( "Done disabling low power polling.\n" ) );
break;
}
}
if ( fMediaPresent == false )
{
DisablePolling ( );
IOSleep ( 1000 );
}
STATUS_LOG ( ( "We are NOT already asleep\n" ) );
fCurrentPowerState = fProposedPowerState;
while ( fNumCommandsOutstanding > 1 )
{
IOSleep ( 1 );
}
if ( START_STOP_UNIT ( request, 0, 0, 1, 1, 0 ) == true )
{
( void ) SendCommand ( request, kTenSecondTimeoutInMS );
}
if ( fDeviceSupportsPowerConditions )
{
STATUS_LOG ( ( "Sending DVD sleep command\n" ) );
if ( START_STOP_UNIT ( request, 0, kMMCPowerConditionsSleep, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
else if ( IsProtocolServiceSupported ( kSCSIProtocolFeature_ProtocolSpecificSleepCommand, NULL ) )
{
STATUS_LOG ( ( "Sending ATA sleep command\n" ) );
features = true;
( void ) HandleProtocolServiceFeature (
kSCSIProtocolFeature_ProtocolSpecificSleepCommand,
( void * ) &features );
}
else
{
STATUS_LOG ( ( "Device does NOT have protocol specific sleep command.\n" ) );
STATUS_LOG ( ( "Spinning down drive.\n" ) );
if ( START_STOP_UNIT ( request, 0, 0, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
break;
case kMMCPowerStateSleep:
{
STATUS_LOG ( ( "case kMMCPowerStateSleep\n" ) );
if ( fCurrentPowerState < kMMCPowerStateSleep )
{
STATUS_LOG ( ( "TicklePowerManager.\n" ) );
TicklePowerManager ( );
STATUS_LOG ( ( "Wakeup path completed.\n" ) );
fCurrentPowerState = fProposedPowerState;
break;
}
STATUS_LOG ( ( "Device is going to sleep now.\n" ) );
if ( fMediaPresent == false )
{
STATUS_LOG ( ( "Media is NOT present.\n" ) );
if ( START_STOP_UNIT ( request, 0, 0, 1, 1, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
if ( fDeviceSupportsLowPowerPolling == false )
{
STATUS_LOG ( ( "Device does NOT support low power polling.\n" ) );
STATUS_LOG ( ( "Spinning down drive.\n" ) );
if ( START_STOP_UNIT ( request, 0, 0, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
fCurrentPowerState = fProposedPowerState;
STATUS_LOG ( ( "Sleep path completed for non-low power polling drive.\n" ) );
break;
}
else
{
STATUS_LOG ( ( "Disabling polling\n" ) );
DisablePolling ( );
}
}
STATUS_LOG ( ( "fDeviceSupportsLowPowerPolling | fMediaPresent\n" ) );
if ( fDeviceSupportsPowerConditions )
{
STATUS_LOG ( ( "Sending START_STOP_UNIT to drive to turn it off\n" ) );
if ( START_STOP_UNIT ( request, 0, 0x05, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
else
{
STATUS_LOG ( ( "Calling protocol layer to sleep drive\n" ) );
features = true;
( void ) HandleProtocolServiceFeature (
kSCSIProtocolFeature_ProtocolSpecificSleepCommand,
( void * ) &features );
}
if ( fMediaPresent == false )
{
STATUS_LOG ( ( "Enabling low power polling.\n" ) );
features = fLowPowerPollingEnabled = true;
( void ) HandleProtocolServiceFeature (
kSCSIProtocolFeature_ProtocolSpecificPolling,
( void * ) &features );
}
fCurrentPowerState = fProposedPowerState;
}
break;
case kMMCPowerStateStandby:
{
UInt32 deviceState = 0;
IOReturn status;
STATUS_LOG ( ( "case kMMCPowerStateStandby\n" ) );
if ( fDeviceSupportsPowerConditions )
{
STATUS_LOG ( ( "case kMMCPowerStateStandby & fDeviceSupportsPowerConditions\n" ) );
status = GetCurrentPowerStateOfDrive ( &deviceState );
if ( ( status != kIOReturnSuccess ) || ( deviceState > kMMCPowerStateStandby ) )
{
STATUS_LOG ( ( "Sending START_STOP_UNIT to put drive in standby mode\n" ) );
if ( START_STOP_UNIT ( request, 0, kMMCPowerConditionsStandby, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
}
fCurrentPowerState = kMMCPowerStateStandby;
}
break;
case kMMCPowerStateIdle:
{
UInt32 deviceState = 0;
IOReturn status;
STATUS_LOG ( ( "case kMMCPowerStateIdle\n" ) );
if ( fDeviceSupportsPowerConditions )
{
STATUS_LOG ( ( "case kMMCPowerStateIdle & fDeviceSupportsPowerConditions\n" ) );
status = GetCurrentPowerStateOfDrive ( &deviceState );
if ( ( status != kIOReturnSuccess ) || ( deviceState > kMMCPowerStateIdle ) )
{
STATUS_LOG ( ( "Sending START_STOP_UNIT to drive to put it in idle mode\n" ) );
if ( START_STOP_UNIT ( request, 0, kMMCPowerConditionsIdle, 0, 0, 0 ) == true )
{
( void ) SendCommand ( request, 0 );
}
}
}
fCurrentPowerState = kMMCPowerStateIdle;
}
break;
case kMMCPowerStateActive:
{
STATUS_LOG ( ( "case kMMCPowerStateActive\n" ) );
if ( fMediaPresent == true )
{
if ( ( fMediaBlockSize * fMediaBlockCount ) != 0 )
{
if ( fCurrentDiscSpeed != 0 )
{
SetMediaAccessSpeed ( fCurrentDiscSpeed );
}
}
}
fCurrentPowerState = kMMCPowerStateActive;
fCommandGate->commandWakeup ( &fCurrentPowerState, false );
}
break;
default:
PANIC_NOW ( ( "Undefined power state issued\n" ) );
break;
}
}
if ( isInactive ( ) )
{
fCurrentPowerState = fProposedPowerState;
fCommandGate->commandWakeup ( &fCurrentPowerState, false );
}
ReleaseSCSITask ( request );
STATUS_LOG ( ( "IOSCSIMultimediaCommandsDevice::HandlePowerChange done\n" ) );
}
bool
IOSCSIMultimediaCommandsDevice::CheckMediaPresence ( void )
{
SCSI_Sense_Data senseBuffer = { 0 };
IOMemoryDescriptor * bufferDesc = NULL;
SCSITaskIdentifier request = NULL;
bool mediaPresent = false;
bool driveReady = false;
SCSIServiceResponse serviceResponse = kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE;
STATUS_LOG ( ( "%s::%s called\n", getName ( ), __FUNCTION__ ) );
bufferDesc = IOMemoryDescriptor::withAddress ( ( void * ) &senseBuffer,
kSenseDefaultSize,
kIODirectionIn );
require_nonzero ( bufferDesc, ErrorExit );
request = GetSCSITask ( );
require_nonzero ( request, ReleaseDescriptor );
do
{
if ( TEST_UNIT_READY ( request, 0 ) == true )
{
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS );
}
if ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE )
{
bool validSense = false;
if ( GetTaskStatus ( request ) == kSCSITaskStatus_CHECK_CONDITION )
{
validSense = GetAutoSenseData ( request, &senseBuffer );
if ( validSense == false )
{
if ( REQUEST_SENSE ( request, bufferDesc, kSenseDefaultSize, 0 ) == true )
{
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS );
}
if ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE )
{
validSense = true;
}
}
if ( validSense == true )
{
STATUS_LOG ( ( "sense data: %01x, %02x, %02x\n",
( senseBuffer.SENSE_KEY & kSENSE_KEY_Mask ),
senseBuffer.ADDITIONAL_SENSE_CODE,
senseBuffer.ADDITIONAL_SENSE_CODE_QUALIFIER ) );
if ( ( ( senseBuffer.SENSE_KEY & kSENSE_KEY_Mask ) == kSENSE_KEY_NOT_READY ) ||
( ( senseBuffer.SENSE_KEY & kSENSE_KEY_Mask ) == kSENSE_KEY_MEDIUM_ERROR ) )
{
if ( ( senseBuffer.ADDITIONAL_SENSE_CODE == 0x04 ) &&
( senseBuffer.ADDITIONAL_SENSE_CODE_QUALIFIER == 0x02 ) )
{
if ( START_STOP_UNIT ( request, 0x00, 0x00, 0x00, 0x01, 0x00 ) == true )
{
( void ) SendCommand ( request, 0 );
}
STATUS_LOG ( ( "%s::drive NOT READY\n", getName ( ) ) );
IOSleep ( 200 );
continue;
}
else if ( ( senseBuffer.ADDITIONAL_SENSE_CODE == 0x3A ) &&
( senseBuffer.ADDITIONAL_SENSE_CODE_QUALIFIER == 0x00 ) )
{
STATUS_LOG ( ( "No Media.\n" ) );
driveReady = true;
mediaPresent = false;
}
else
{
STATUS_LOG ( ( "%s::drive NOT READY\n", getName ( ) ) );
IOSleep ( 200 );
continue;
}
}
else if ( ( ( senseBuffer.SENSE_KEY & kSENSE_KEY_Mask ) == kSENSE_KEY_UNIT_ATTENTION ) &&
( senseBuffer.ADDITIONAL_SENSE_CODE == 0x28 ) &&
( senseBuffer.ADDITIONAL_SENSE_CODE_QUALIFIER == 0x00 ) )
{
STATUS_LOG ( ( "%s::drive NOT READY\n", getName ( ) ) );
IOSleep ( 200 );
continue;
}
else
{
STATUS_LOG ( ( "%s::drive READY, media present\n", getName ( ) ) );
driveReady = true;
mediaPresent = true;
}
}
}
else
{
STATUS_LOG ( ( "%s::drive READY, media present\n", getName ( ) ) );
driveReady = true;
mediaPresent = true;
}
}
else
{
IOSleep ( 200 );
}
} while ( ( driveReady == false ) && ( isInactive ( ) == false ) );
ReleaseSCSITask ( request );
ReleaseDescriptor:
require_nonzero ( bufferDesc, ErrorExit );
bufferDesc->release ( );
bufferDesc = NULL;
ErrorExit:
return mediaPresent;
}
IOReturn
IOSCSIMultimediaCommandsDevice::GetCurrentPowerStateOfDrive ( UInt32 * powerState )
{
UInt8 powerStatus[kPowerStatusBufferSize];
IOMemoryDescriptor * bufferDesc = NULL;
SCSITaskIdentifier request = NULL;
SCSIServiceResponse serviceResponse = kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE;
IOReturn status = kIOReturnError;
STATUS_LOG ( ( "%s::%s called\n", getName ( ), __FUNCTION__ ) );
require ( fDeviceSupportsPowerConditions, Exit );
bufferDesc = IOMemoryDescriptor::withAddress ( ( void * ) powerStatus,
kPowerStatusBufferSize,
kIODirectionIn );
require_nonzero ( bufferDesc, Exit );
request = GetSCSITask ( );
require_nonzero ( request, ReleaseDescriptor );
if ( GET_EVENT_STATUS_NOTIFICATION ( request,
bufferDesc,
0,
0x02,
kPowerStatusBufferSize,
0 ) == true )
{
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS );
}
require ( ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE ), ReleaseTask );
require ( ( GetTaskStatus ( request ) == kSCSITaskStatus_GOOD ), ReleaseTask );
*powerState = kMMCNumPowerStates - powerStatus[kPowerStatusByte];
status = kIOReturnSuccess;
ReleaseTask:
require_nonzero ( request, ReleaseDescriptor );
ReleaseSCSITask ( request );
request = NULL;
ReleaseDescriptor:
require_nonzero ( bufferDesc, Exit );
bufferDesc->release ( );
bufferDesc = NULL;
Exit:
return status;
}