/* * Copyright (c) 1998-2008 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ //----------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------- // General OS Services header files #include // This class' header file #include "IOUSBMassStorageClass.h" #include "Debugging.h" #include #include #include //-------------------------------------------------------------------------------------------------- // Defines //-------------------------------------------------------------------------------------------------- // Masks for GetDeviceInformation response. GetDeviceInformation returns a UInt32. enum { kUSBDeviceInfoIsCaptiveMask = ( 1 << kUSBInformationDeviceIsCaptiveBit ), kUSBDeviceInfoIsAttachedToRootHubMask = ( 1 << kUSBInformationDeviceIsAttachedToRootHubBit ), kUSBDeviceInfoIsInternalMask = ( 1 << kUSBInformationDeviceIsInternalBit ), kUSBDeviceInfoIsConnectedMask = ( 1 << kUSBInformationDeviceIsConnectedBit ), kUSBDeviceInfoIsEnabledMask = ( 1 << kUSBInformationDeviceIsEnabledBit ), kUSBDeviceInfoIsSuspendedMask = ( 1 << kUSBInformationDeviceIsSuspendedBit ), kUSBDeviceInfoIsInResetMask = ( 1 << kUSBInformationDeviceIsInResetBit ), kUSBDeviceInfoOvercurrentMask = ( 1 << kUSBInformationDeviceOvercurrentBit ), kUSBDeviceInfoIsePortIsInTestModeMask = ( 1 << kUSBInformationDevicePortIsInTestModeBit ) }; // Default timeout values enum { kDefaultReadTimeoutDuration = 30000, kDefaultWriteTimeoutDuration = 30000 }; //----------------------------------------------------------------------------- // Macros //----------------------------------------------------------------------------- // Macros for printing debugging information #if (USB_MASS_STORAGE_DEBUG == 1) #define PANIC_NOW(x) IOPanic x // Override the debug level for USBLog to make sure our logs make it out and then import // the logging header. #define DEBUG_LEVEL 1 #include #define STATUS_LOG(x) USBLog x #else #define STATUS_LOG(x) #define PANIC_NOW(x) #endif #define DEBUGGING_LEVEL 0 #define DEBUGLOG kprintf #define super IOSCSIProtocolServices OSDefineMetaClassAndStructors( IOUSBMassStorageClass, IOSCSIProtocolServices ) //----------------------------------------------------------------------------- // - init - Called at initialization time [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::init( OSDictionary * propTable ) { if( super::init( propTable ) == false) { return false; } return true; } //----------------------------------------------------------------------------- // - start - Called at services start time (after successful matching) // [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::start( IOService * provider ) { IOUSBFindEndpointRequest request; OSDictionary * characterDict = NULL; OSObject * obj = NULL; IOReturn result = kIOReturnError; OSNumber * number = NULL; if( super::start( provider ) == false ) { STATUS_LOG(( 1, "%s[%p]: superclass start failure.", getName(), this)); return false; } // Allocate data for our expansion data. reserved = ( ExpansionData * ) IOMalloc ( sizeof ( ExpansionData ) ); bzero ( reserved, sizeof ( ExpansionData ) ); // Save the reference to the interface on the device that will be // the provider for this object. SetInterfaceReference( OSDynamicCast( IOUSBInterface, provider) ); if ( GetInterfaceReference() == NULL ) { STATUS_LOG(( 1, "%s[%p]: the provider is not an IOUSBInterface object", getName(), this )); // If our provider is not a IOUSBInterface object, return false // to indicate that the object could not be correctly // instantiated. // The USB Mass Storage Class specification requires that all // devices be a composite device with a Mass Storage interface // so this object will always be an interface driver. return false; } STATUS_LOG(( 6, "%s[%p]: USB Mass Storage @ %d", getName(), this, GetInterfaceReference()->GetDevice()->GetAddress())); if ( GetInterfaceReference()->open( this ) == false) { STATUS_LOG(( 1, "%s[%p]: could not open the interface", getName(), this )); return false; } // Set the IOUSBPipe object pointers to NULL so that the driver can // release these objects if instantition is not successful. fBulkInPipe = NULL; fBulkOutPipe = NULL; fInterruptPipe = NULL; // Default is to have no clients fClients = NULL; // Default is to have a max lun of 0. SetMaxLogicalUnitNumber( 0 ); // Initialize all Bulk Only related member variables to their default // states. fBulkOnlyCommandTag = 0; fBulkOnlyCommandStructInUse = false; // Initialize all CBI related member variables to their default // states. fCBICommandStructInUse = false; // Flag we use to indicate whether or not the device requires the standard // USB device reset instead of the BO reset. This applies to BO devices only. fUseUSBResetNotBOReset = false; // Certain Bulk-Only device are subject to erroneous CSW tags. fKnownCSWTagMismatchIssues = false; // We shouldn't be waiting for a reset and reconfiguration yet. fWaitingForReconfigurationMessage = false; fTerminating = false; // Check if the personality for this device specifies a preferred protocol if ( getProperty( kIOUSBMassStorageCharacteristics ) == NULL ) { // This device does not specify a preferred protocol, use the protocol // defined in the descriptor. fPreferredProtocol = GetInterfaceReference()->GetInterfaceProtocol(); fPreferredSubclass = GetInterfaceReference()->GetInterfaceSubClass(); } else { OSDictionary * characterDict; characterDict = OSDynamicCast( OSDictionary, getProperty( kIOUSBMassStorageCharacteristics )); // Check if the personality for this device specifies a preferred // protocol if ( characterDict->getObject( kIOUSBMassStoragePreferredProtocol ) == NULL ) { // This device does not specify a preferred protocol, use the // protocol defined in the interface descriptor. fPreferredProtocol = GetInterfaceReference()->GetInterfaceProtocol(); } else { OSNumber * preferredProtocol; preferredProtocol = OSDynamicCast( OSNumber, characterDict->getObject( kIOUSBMassStoragePreferredProtocol )); // This device has a preferred protocol, use that. fPreferredProtocol = preferredProtocol->unsigned32BitValue(); } // Check if this device is known not to support the bulk-only USB reset. if ( characterDict->getObject( kIOUSBMassStorageDoNotOperate ) != NULL ) { goto abortStart; } // Check if this device is known not to support the bulk-only USB reset. if ( characterDict->getObject( kIOUSBMassStorageUseStandardUSBReset ) != NULL ) { fUseUSBResetNotBOReset = true; } // Is this a device which has CBW/CSW tag issues? if ( characterDict->getObject( kIOUSBKnownCSWTagIssues ) != NULL ) { fKnownCSWTagMismatchIssues = true; } // Check if the personality for this device specifies a preferred // subclass if ( characterDict->getObject( kIOUSBMassStoragePreferredSubclass ) == NULL ) { // This device does not specify a preferred subclass, use the // subclass defined in the interface descriptor. fPreferredSubclass = GetInterfaceReference()->GetInterfaceSubClass(); } else { OSNumber * preferredSubclass; preferredSubclass = OSDynamicCast( OSNumber, characterDict->getObject( kIOUSBMassStoragePreferredSubclass )); // This device has a preferred protocol, use that. fPreferredSubclass = preferredSubclass->unsigned32BitValue(); } } STATUS_LOG(( 6, "%s[%p]: Preferred Protocol is: %d", getName(), this, fPreferredProtocol)); STATUS_LOG(( 6, "%s[%p]: Preferred Subclass is: %d", getName(), this, fPreferredSubclass)); // Verify that the device has a supported interface type and configure that // Interrupt pipe if the protocol requires one. STATUS_LOG(( 7, "%s[%p]: Configure the Storage interface", getName(), this)); switch ( GetInterfaceProtocol() ) { case kProtocolControlBulkInterrupt: { // Find the interrupt pipe for the device request.type = kUSBInterrupt; request.direction = kUSBIn; fInterruptPipe = GetInterfaceReference()->FindNextPipe(NULL, &request); STATUS_LOG(( 7, "%s[%p]: find interrupt pipe", getName(), this)); if(( GetInterfaceProtocol() == kProtocolControlBulkInterrupt) && (fInterruptPipe == 0)) { // This is a CBI device and must have an interrupt pipe, // halt configuration since one could not be found STATUS_LOG((1, "%s[%p]: No interrupt pipe for CBI, abort", getName(), this)); goto abortStart; } fCBIMemoryDescriptor = IOMemoryDescriptor::withAddress( &fCBICommandRequestBlock.cbiGetStatusBuffer, kUSBStorageAutoStatusSize, kIODirectionIn); require_nonzero ( fCBIMemoryDescriptor, abortStart ); result = fCBIMemoryDescriptor->prepare(); require_success ( result, abortStart ); } break; case kProtocolControlBulk: // Since all the CB devices I have seen do not use the interrupt // endpoint, even if it exists, ignore it if present. case kProtocolBulkOnly: { STATUS_LOG(( 7, "%s[%p]: Bulk Only - skip interrupt pipe", getName(), this)); // Since this is a Bulk Only device, do not look for // interrupt and set the pipe object to NULL so that the // driver can not try to use it. fInterruptPipe = NULL; // Allocate the memory descriptor needed to send the CBW out. fBulkOnlyCBWMemoryDescriptor = IOMemoryDescriptor::withAddress( &fBulkOnlyCommandRequestBlock.boCBW, kByteCountOfCBW, kIODirectionOut); require_nonzero ( fBulkOnlyCBWMemoryDescriptor, abortStart ); result = fBulkOnlyCBWMemoryDescriptor->prepare(); require_success ( result, abortStart ); // Allocate the memory descriptor needed to retrieve the CSW. fBulkOnlyCSWMemoryDescriptor = IOMemoryDescriptor::withAddress( &fBulkOnlyCommandRequestBlock.boCSW, kByteCountOfCSW, kIODirectionIn); require_nonzero ( fBulkOnlyCSWMemoryDescriptor, abortStart ); result = fBulkOnlyCSWMemoryDescriptor->prepare(); require_success ( result, abortStart ); } break; default: { // The device has a protocol that the driver does not // support. Return false to indicate that instantiation was // not successful. goto abortStart; } break; } // Find the Bulk In pipe for the device STATUS_LOG(( 7, "%s[%p]: find bulk in pipe", getName(), this)); request.type = kUSBBulk; request.direction = kUSBIn; fBulkInPipe = GetInterfaceReference()->FindNextPipe(NULL, &request); if ( fBulkInPipe == NULL ) { // We could not find the bulk in pipe, not much a bulk transfer // device can do without this, so fail the configuration. STATUS_LOG((1, "%s[%p]: No bulk in pipe found, aborting", getName(), this)); goto abortStart; } // Find the Bulk Out pipe for the device STATUS_LOG(( 7, "%s[%p]: find bulk out pipe", getName(), this)); request.type = kUSBBulk; request.direction = kUSBOut; fBulkOutPipe = GetInterfaceReference()->FindNextPipe(NULL, &request); if ( fBulkOutPipe == NULL ) { // We could not find the bulk out pipe, not much a bulk transfer // device can do without this, so fail the configuration. STATUS_LOG(( 1, "%s[%p]: No bulk out pipe found, aborting", getName(), this)); goto abortStart; } // Build the Protocol Charactersitics dictionary since not all devices will have a // SCSI Peripheral Device Nub to guarantee its existance. characterDict = OSDynamicCast ( OSDictionary, getProperty ( kIOPropertyProtocolCharacteristicsKey ) ); if ( characterDict == NULL ) { characterDict = OSDictionary::withCapacity ( 1 ); } else { characterDict->retain ( ); } if ( characterDict != NULL ) { obj = getProperty ( kIOPropertyPhysicalInterconnectTypeKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyPhysicalInterconnectTypeKey, obj ); } obj = getProperty ( kIOPropertyPhysicalInterconnectLocationKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyPhysicalInterconnectLocationKey, obj ); } // Check to see if this device is internal or not. The results of this check if successful // will override location data provided in IOKit personalities. if ( IsPhysicalInterconnectLocationInternal ( ) ) { OSString * internalString = NULL; // As this shall be a rarity, log it. Internal devices should only be configured at // boot time so USB Logger isn't an option. if ( USB_MASS_STORAGE_DEBUG == 1 ) { IOLog ( "%s[%p]: Configuring INTERNAL USB storage device", getName(), this ); } internalString = OSString::withCString ( kIOPropertyInternalKey ); if ( internalString != NULL ) { characterDict->setObject ( kIOPropertyPhysicalInterconnectLocationKey, internalString ); internalString->release ( ); internalString = NULL; } } obj = getProperty ( kIOPropertyReadTimeOutDurationKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyReadTimeOutDurationKey, obj ); } else { number = OSNumber::withNumber ( kDefaultReadTimeoutDuration, 32 ); if ( number != NULL ) { characterDict->setObject ( kIOPropertyReadTimeOutDurationKey, number ); number->release ( ); number = NULL; } } obj = getProperty ( kIOPropertyWriteTimeOutDurationKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyWriteTimeOutDurationKey, obj ); } else { number = OSNumber::withNumber ( kDefaultWriteTimeoutDuration, 32 ); if ( number != NULL ) { characterDict->setObject ( kIOPropertyWriteTimeOutDurationKey, number ); number->release ( ); number = NULL; } } setProperty ( kIOPropertyProtocolCharacteristicsKey, characterDict ); characterDict->release ( ); characterDict = NULL; } STATUS_LOG(( 6, "%s[%p]: successfully configured", getName(), this)); #if defined (__i386__) { // As USB booting is only supporting on i386 based, do not compile for PPC. char usbDeviceAddress[10]; OSNumber * usbDeviceID; sprintf ( usbDeviceAddress, "%x", ( int ) GetInterfaceReference()->GetDevice()->GetAddress() ); usbDeviceID = OSNumber::withNumber ( ( int ) GetInterfaceReference()->GetDevice()->GetAddress(), 64 ); if ( usbDeviceID != NULL ) { setProperty ( kIOPropertyIOUnitKey, usbDeviceID ); setLocation ( ( const char * ) usbDeviceAddress, gIODTPlane ); usbDeviceID->release ( ); } } #endif // Device configured. We're attached. fDeviceAttached = true; InitializePowerManagement( GetInterfaceReference() ); BeginProvidedServices(); return true; abortStart: STATUS_LOG(( 1, "%s[%p]: aborting startup. Stop the provider.", getName(), this )); if ( fCBIMemoryDescriptor != NULL ) { fCBIMemoryDescriptor->complete(); fCBIMemoryDescriptor->release(); fCBIMemoryDescriptor = NULL; } if ( fBulkOnlyCBWMemoryDescriptor != NULL ) { fBulkOnlyCBWMemoryDescriptor->complete(); fBulkOnlyCBWMemoryDescriptor->release(); fBulkOnlyCBWMemoryDescriptor = NULL; } if ( fBulkOnlyCSWMemoryDescriptor != NULL ) { fBulkOnlyCSWMemoryDescriptor->complete(); fBulkOnlyCSWMemoryDescriptor->release(); fBulkOnlyCSWMemoryDescriptor = NULL; } // Call the stop method to clean up any allocated resources. stop( provider ); return false; } //----------------------------------------------------------------------------- // - stop - Called at stop time [PUBLIC] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::stop(IOService * provider) { // I am logging this as a 1 because if anything is logging after this we want to know about it. // This should be the last message we see. STATUS_LOG(( 1, "%s[%p]: Bye bye!", getName(), this)); EndProvidedServices(); // Tell the interface object to close all pipes since the driver is // going away. fBulkInPipe = NULL; fBulkOutPipe = NULL; fInterruptPipe = NULL; super::stop(provider); } //----------------------------------------------------------------------------- // - free - Called by IOKit to free any resources. [PUBLIC] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::free ( void ) { if ( reserved != NULL ) { // Since fClients is defined as reserved->fClients we don't want // to dereference it unless reserved is non-NULL. if ( fClients != NULL ) { fClients->release(); fClients = NULL; } if ( fCBIMemoryDescriptor != NULL ) { fCBIMemoryDescriptor->complete(); fCBIMemoryDescriptor->release(); fCBIMemoryDescriptor = NULL; } if ( fBulkOnlyCBWMemoryDescriptor != NULL ) { fBulkOnlyCBWMemoryDescriptor->complete(); fBulkOnlyCBWMemoryDescriptor->release(); fBulkOnlyCBWMemoryDescriptor = NULL; } if ( fBulkOnlyCSWMemoryDescriptor != NULL ) { fBulkOnlyCSWMemoryDescriptor->complete(); fBulkOnlyCSWMemoryDescriptor->release(); fBulkOnlyCSWMemoryDescriptor = NULL; } IOFree ( reserved, sizeof ( ExpansionData ) ); reserved = NULL; } super::free ( ); } //----------------------------------------------------------------------------- // - message - Called by IOKit to deliver messages. [PUBLIC] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::message( UInt32 type, IOService * provider, void * argument ) { IOReturn result; STATUS_LOG ( (4, "%s[%p]: message = %lx called", getName(), this, type ) ); switch( type ) { // Waiting for kIOUSBMessageCompositeDriverReconfigured to be define in USB.h case kIOUSBMessageCompositeDriverReconfigured: { STATUS_LOG((2, "%s[%p]: message kIOUSBMessageCompositeDriverReconfigured.", getName(), this)); if ( fWaitingForReconfigurationMessage ) { fWaitingForReconfigurationMessage = false; FinishDeviceRecovery ( kIOReturnSuccess ); } } break; default: { STATUS_LOG((2, "%s[%p]: message default case.", getName(), this)); result = super::message( type, provider, argument ); } } return result; } //----------------------------------------------------------------------------- // - willTerminate [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::willTerminate( IOService * provider, IOOptionBits options ) { IOUSBInterface * currentInterface; STATUS_LOG((2, "%s[%p]: willTerminate called.", getName(), this )); currentInterface = GetInterfaceReference(); if ( currentInterface != NULL ) { STATUS_LOG((2, "%s[%p]: willTerminate interface is non NULL.", getName(), this)); // Mark ourselves as terminating so we don't accept any additional IO. fTerminating = true; // Let the clients know that the device is gone. SendNotification_DeviceRemoved( ); } return super::willTerminate( provider, options ); } //----------------------------------------------------------------------------- // - didTerminate [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::didTerminate( IOService * provider, IOOptionBits options, bool * defer ) { // this method comes at the end of the termination sequence. Hopefully, all of our outstanding IO is complete // in which case we can just close our provider and IOKit will take care of the rest. Otherwise, we need to // hold on to the device and IOKit will terminate us when we close it later // STATUS_LOG ((3 , "%s[%p]::didTerminate isInactive = %d", getName(), this, isInactive() )); // This should already be set to true, but can't hurt ... fTerminating = true; if ( ( GetInterfaceReference() != NULL ) && ( fResetInProgress == false ) ) { IOUSBInterface * currentInterface; currentInterface = GetInterfaceReference(); // We set this to NULL first chance we get to prevent a potential race between // our reset thread and our didTeriminate(). SetInterfaceReference( NULL ); // Close and nullify our USB Interface. currentInterface->close( this ); } return super::didTerminate(provider, options, defer); } //----------------------------------------------------------------------------- // - BeginProvidedServices [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::BeginProvidedServices( void ) { // If this is a BO device that supports multiple LUNs, we will need // to spawn off a nub for each valid LUN. If this is a CBI/CB // device or a BO device that only supports LUN 0, this object can // register itself as the nub. STATUS_LOG(( 7, "%s[%p]: Determine the maximum LUN", getName(), this )); if( GetInterfaceProtocol() == kProtocolBulkOnly ) { IOReturn status; bool maxLUNDetermined = false; // Before we issue the GetMaxLUN call let's check if this device // specifies a MaxLogicalUnitNumber as part of its personality. if( getProperty( kIOUSBMassStorageCharacteristics ) != NULL ) { OSDictionary * characterDict = OSDynamicCast( OSDictionary, getProperty( kIOUSBMassStorageCharacteristics )); // Check if this device is known to have problems when waking from sleep if( characterDict->getObject( kIOUSBMassStorageMaxLogicalUnitNumber ) != NULL ) { OSNumber * maxLUN = OSDynamicCast( OSNumber, characterDict->getObject( kIOUSBMassStorageMaxLogicalUnitNumber ) ); if( maxLUN != NULL ) { SetMaxLogicalUnitNumber( maxLUN->unsigned8BitValue() ); STATUS_LOG(( 4, "%s[%p]: Number of LUNs %u.", getName(), this, maxLUN->unsigned8BitValue() )); maxLUNDetermined = true; } } } if( maxLUNDetermined == false ) { // The device is a Bulk Only transport device, issue the // GetMaxLUN call to determine what the maximum value is. bool triedReset = false; UInt8 clearPipeAttempts = 0; // We want to loop until we get a satisfactory response to GetMaxLUN, either an answer or failure. while ( status != kIOReturnSuccess ) { // Build the USB command fUSBDeviceRequest.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBClass, kUSBInterface); fUSBDeviceRequest.bRequest = 0xFE; fUSBDeviceRequest.wValue = 0; fUSBDeviceRequest.wIndex = GetInterfaceReference()->GetInterfaceNumber(); fUSBDeviceRequest.wLength = 1; fUSBDeviceRequest.pData = &fMaxLogicalUnitNumber; STATUS_LOG(( 4, "%s[%p]: Issuing device request to find max LUN", getName(), this )); // Send the command over the control endpoint status = GetInterfaceReference()->DeviceRequest( &fUSBDeviceRequest ); STATUS_LOG(( 4, "%s[%p]: DeviceRequest GetMaxLUN returned status = %x", getName(), this, status )); if ( status != kIOReturnSuccess ) { SetMaxLogicalUnitNumber( 0 ); if( ( status == kIOUSBPipeStalled ) && ( clearPipeAttempts < 3 ) ) { UInt8 eStatus[2]; STATUS_LOG(( 4, "%s[%p]: calling GetStatusEndpointStatus to clear stall", getName(), this )); // Throw in an extra Get Status to clear up devices that stall the // control pipe like the early Iomega devices. GetStatusEndpointStatus( GetControlPipe(), &eStatus[0], NULL); clearPipeAttempts++; } else if ( ( status == kIOReturnNotResponding ) && ( triedReset == false ) ) { // The device isn't responding. Let us reset the device, and try again. // The endpoint status could not be retrieved meaning that the device has // stopped responding. Or this could be a device we know needs a reset. // Begin the device reset sequence. STATUS_LOG(( 4, "%s[%p]: BeginProvidedServices: device not responding, reseting.", getName(), this )); // Reset the device on its own thread so we don't deadlock. ResetDeviceNow ( true ); triedReset = true; } else { break; } } } } } else { // CBI and CB protocols do not support LUNs so for these the // maximum LUN will always be zero. SetMaxLogicalUnitNumber( 0 ); } STATUS_LOG(( 5, "%s[%p]: Maximum supported LUN is: %d", getName(), this, GetMaxLogicalUnitNumber() )); STATUS_LOG(( 7, "%s[%p]: successfully configured", getName(), this )); // If this is a BO device that supports multiple LUNs, we will need // to spawn off a nub for each valid LUN. If this is a CBI/CB // device or a BO device that only supports LUN 0, this object can // register itself as the nub. if ( GetMaxLogicalUnitNumber() == 0 ) { registerService(kIOServiceAsynchronous); fClients = NULL; } else { // Allocate space for our set that will keep track of the LUNs. fClients = OSSet::withCapacity( GetMaxLogicalUnitNumber() + 1 ); for( int loopLUN = 0; loopLUN <= GetMaxLogicalUnitNumber(); loopLUN++ ) { STATUS_LOG(( 6, "%s[%p]::CreatePeripheralDeviceNubForLUN entering.", getName(), this )); IOSCSILogicalUnitNub * nub = OSTypeAlloc( IOSCSILogicalUnitNub ); if( nub == NULL ) { PANIC_NOW(( "IOUSBMassStorageClass::CreatePeripheralDeviceNubForLUN failed" )); return false; } if ( nub->init( 0 ) == false ) { // Release our nub before we return so we don't leak... nub->release(); // We didn't init successfully so we should return false. return false; } if( nub->attach( this ) == false ) { if( isInactive() == false ) { // panic since the nub can't attach and we are active PANIC_NOW(( "IOUSBMassStorageClass::CreatePeripheralDeviceNubForLUN unable to attach nub" )); } // Release our nub before we return so we don't leak... nub->release(); // We didn't attach so we should return false. return false; } nub->SetLogicalUnitNumber( loopLUN ); if( nub->start( this ) == false ) { nub->detach( this ); } else { nub->registerService( kIOServiceAsynchronous ); } nub->release(); nub = NULL; STATUS_LOG(( 6, "%s[%p]::CreatePeripheralDeviceNubForLUN exiting.", getName(), this )); } } return true; } //----------------------------------------------------------------------------- // - EndProvidedServices [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::EndProvidedServices( void ) { return true; } #pragma mark - #pragma mark *** CDB Transport Methods *** #pragma mark - //----------------------------------------------------------------------------- // - SendSCSICommand [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::SendSCSICommand( SCSITaskIdentifier request, SCSIServiceResponse * serviceResponse, SCSITaskStatus * taskStatus ) { IOReturn status; // Set the defaults to an error state. *taskStatus = kSCSITaskStatus_No_Status; *serviceResponse = kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; STATUS_LOG(( 6, "%s[%p]: SendSCSICommand was called", getName(), this )); // If we have been marked as inactive, or no longer have the device, return false. if ( isInactive () || ( fDeviceAttached == false ) || ( fTerminating == true ) ) { return false; } #if (USB_MASS_STORAGE_DEBUG == 1) SCSICommandDescriptorBlock cdbData; STATUS_LOG(( 4, "%s[%p]: SendSCSICommand CDB data: ", getName(), this )); GetCommandDescriptorBlock( request, &cdbData ); if ( GetCommandDescriptorBlockSize ( request ) == kSCSICDBSize_6Byte ) STATUS_LOG(( 4, "%s[%p]: %X : %X : %X : %X : %X : %X", getName(), this, cdbData[0], cdbData[1], cdbData[2], cdbData[3], cdbData[4], cdbData[5])); else if ( GetCommandDescriptorBlockSize ( request ) == kSCSICDBSize_10Byte ) STATUS_LOG(( 4, "%s[%p]: %X : %X : %X : %X : %X : %X : %X : %X : %X : %X", getName(), this, cdbData[0], cdbData[1], cdbData[2], cdbData[3], cdbData[4], cdbData[5], cdbData[6], cdbData[7], cdbData[8], cdbData[9])); else if ( GetCommandDescriptorBlockSize ( request ) == kSCSICDBSize_12Byte ) STATUS_LOG(( 4, "%s[%p]: %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X", getName(), this, cdbData[0], cdbData[1], cdbData[2], cdbData[3], cdbData[4], cdbData[5], cdbData[6], cdbData[7], cdbData[8], cdbData[9], cdbData[10], cdbData[11])); else if ( GetCommandDescriptorBlockSize ( request ) == kSCSICDBSize_16Byte ) STATUS_LOG(( 4, "%s[%p]: %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X : %X", getName(), this, cdbData[0], cdbData[1], cdbData[2], cdbData[3], cdbData[4], cdbData[5], cdbData[6], cdbData[7], cdbData[8], cdbData[9], cdbData[10], cdbData[11], cdbData[12], cdbData[13], cdbData[14], cdbData[15])); #endif if( GetInterfaceProtocol() == kProtocolBulkOnly) { if ( fBulkOnlyCommandStructInUse == true ) { return false; } fBulkOnlyCommandStructInUse = true; STATUS_LOG(( 6, "%s[%p]: SendSCSICommandforBulkOnlyProtocol sent", getName(), this )); status = SendSCSICommandForBulkOnlyProtocol( request ); if( status != kIOReturnSuccess ) { // If the command fails we want to make sure that we // don't hold up other commands. fBulkOnlyCommandStructInUse = false; } STATUS_LOG(( 5, "%s[%p]: SendSCSICommandforBulkOnlyProtocol returned %x", getName(), this, status )); } else { if ( fCBICommandStructInUse == true ) { return false; } fCBICommandStructInUse = true; status = SendSCSICommandForCBIProtocol( request ); if( status != kIOReturnSuccess ) { // If the command fails we want to make sure that we // don't hold up other commands. fCBICommandStructInUse = false; } STATUS_LOG(( 5, "%s[%p]: SendSCSICommandforCBIProtocol returned %x", getName(), this, status )); } if ( status == kIOReturnSuccess ) { *serviceResponse = kSCSIServiceResponse_Request_In_Process; } return true; } //----------------------------------------------------------------------------- // - CompleteSCSICommand [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::CompleteSCSICommand( SCSITaskIdentifier request, IOReturn status ) { fBulkOnlyCommandStructInUse = false; fCBICommandStructInUse = false; if ( status == kIOReturnSuccess ) { CommandCompleted( request, kSCSIServiceResponse_TASK_COMPLETE, kSCSITaskStatus_GOOD ); } else { STATUS_LOG(( 2, "%s[%p]: CompleteSCSICommand kSCSITaskStatus_CHECK_CONDITION", getName(), this )); CommandCompleted( request, kSCSIServiceResponse_TASK_COMPLETE, kSCSITaskStatus_CHECK_CONDITION ); } } //----------------------------------------------------------------------------- // - AbortSCSICommand [PROTECTED] //----------------------------------------------------------------------------- SCSIServiceResponse IOUSBMassStorageClass::AbortSCSICommand( SCSITaskIdentifier abortTask ) { IOReturn status = kIOReturnSuccess; STATUS_LOG(( 6, "%s[%p]: AbortSCSICommand was called", getName(), this )); if ( abortTask == NULL ) { // We were given an invalid SCSI Task object. Let the client know. STATUS_LOG(( 1, "%s[%p]: AbortSCSICommand was called with a NULL CDB object", getName(), this )); return kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; } if ( fTerminating == true ) { // We have an invalid interface, the device has probably been removed. // Nothing else to do except to report an error. STATUS_LOG(( 1, "%s[%p]: AbortSCSICommand was called with a NULL interface", getName(), this )); return kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; } if(GetInterfaceReference()->GetInterfaceProtocol() == kProtocolBulkOnly) { status = AbortSCSICommandForBulkOnlyProtocol( abortTask ); STATUS_LOG(( 5, "%s[%p]: abortCDBforBulkOnlyProtocol returned %x", getName(), this, status )); } else { status = AbortSCSICommandForCBIProtocol( abortTask ); STATUS_LOG(( 5, "%s[%p]: abortCDBforCBIProtocol returned %x", getName(), this, status )); } // Since the driver currently does not support abort, return an error return kSCSIServiceResponse_FUNCTION_REJECTED; } //----------------------------------------------------------------------------- // - IsProtocolServiceSupported [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::IsProtocolServiceSupported( SCSIProtocolFeature feature, void * serviceValue ) { bool isSupported = false; STATUS_LOG(( 6, "%s[%p]::IsProtocolServiceSupported called", getName(), this )); // all features just return false switch( feature ) { case kSCSIProtocolFeature_GetMaximumLogicalUnitNumber: { *((UInt32 *) serviceValue) = GetMaxLogicalUnitNumber(); isSupported = true; } break; case kSCSIProtocolFeature_MaximumReadBlockTransferCount: case kSCSIProtocolFeature_MaximumWriteBlockTransferCount: case kSCSIProtocolFeature_MaximumReadTransferByteCount: case kSCSIProtocolFeature_MaximumWriteTransferByteCount: { // Do we have SCSI Device Characteristics dictionary which may state preferences for // max transfer sizes? if ( ( getProperty ( kIOPropertySCSIDeviceCharacteristicsKey ) ) != NULL ) { OSDictionary * characterDict = NULL; OSNumber * number = NULL; UInt32 maxBlockCount = 0; UInt64 maxByteCount = 0; // Get the SCSI Characteristics Dictionary. characterDict = OSDynamicCast ( OSDictionary, ( getProperty ( kIOPropertySCSIDeviceCharacteristicsKey ) ) ); if ( feature == kSCSIProtocolFeature_MaximumReadBlockTransferCount ) { // Does the IOKit personality specify a maximum number of blocks? number = OSDynamicCast ( OSNumber, characterDict->getObject ( kIOMaximumBlockCountReadKey ) ); require ( ( number != NULL ), Exit ); maxBlockCount = number->unsigned32BitValue ( ); require ( ( maxBlockCount != 0 ), Exit ); *((UInt32 *) serviceValue) = maxBlockCount; isSupported = true; } if ( feature == kSCSIProtocolFeature_MaximumWriteBlockTransferCount ) { // Does the IOKit personality specify a maximum number of blocks? number = OSDynamicCast ( OSNumber, characterDict->getObject ( kIOMaximumBlockCountWriteKey ) ); require ( number, Exit ); maxBlockCount = number->unsigned32BitValue ( ); require ( maxBlockCount, Exit ); *((UInt32 *) serviceValue) = maxBlockCount; isSupported = true; } if ( feature == kSCSIProtocolFeature_MaximumReadTransferByteCount ) { // Does the IOKit personality specify a maximum number of bytes? number = OSDynamicCast ( OSNumber, characterDict->getObject ( kIOMaximumByteCountReadKey ) ); require ( number, Exit ); maxByteCount = number->unsigned64BitValue ( ); require ( maxByteCount, Exit ); *((UInt64 *) serviceValue) = maxByteCount; isSupported = true; } if ( feature == kSCSIProtocolFeature_MaximumWriteTransferByteCount ) { // Does the IOKit personality specify a maximum number of bytes? number = OSDynamicCast ( OSNumber, characterDict->getObject ( kIOMaximumByteCountWriteKey ) ); require ( number, Exit ); maxByteCount = number->unsigned64BitValue ( ); require ( maxByteCount, Exit ); *((UInt64 *) serviceValue) = maxByteCount; isSupported = true; } } } break; default: { isSupported = false; } break; } Exit: return isSupported; } //----------------------------------------------------------------------------- // - HandleProtocolServiceFeature [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::HandleProtocolServiceFeature( SCSIProtocolFeature feature, void * serviceValue ) { UNUSED( feature ); UNUSED( serviceValue ); return false; } #pragma mark - #pragma mark *** Standard USB Command Methods *** #pragma mark - //----------------------------------------------------------------------------- // - ClearFeatureEndpointStall - Method to do the CLEAR_FEATURE command for // an ENDPOINT_STALL feature. // [PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::ClearFeatureEndpointStall( IOUSBPipe * thePipe, IOUSBCompletion * completion ) { IOReturn status; if ( ( fTerminating == true ) || ( thePipe == NULL ) ) { // We're terminating, the device has probably been removed. // Nothing else to do except to report an error. return kIOReturnDeviceError; } // Make sure that the Data Toggles are reset before doing the // Clear Stall. thePipe->Reset(); // Clear out the structure for the request bzero( &fUSBDeviceRequest, sizeof(IOUSBDevRequest)); // Build the USB command fUSBDeviceRequest.bmRequestType = USBmakebmRequestType(kUSBNone, kUSBStandard, kUSBEndpoint); fUSBDeviceRequest.bRequest = kUSBRqClearFeature; fUSBDeviceRequest.wValue = 0; // Zero is EndpointStall fUSBDeviceRequest.wIndex = thePipe->GetEndpointNumber(); if ( thePipe == GetBulkInPipe() ) { fUSBDeviceRequest.wIndex |= 0x80; } // Send the command over the control endpoint status = GetInterfaceReference()->DeviceRequest( &fUSBDeviceRequest, completion ); STATUS_LOG(( 5, "%s[%p]: ClearFeatureEndpointStall returned %x", getName(), this, status )); return status; } //----------------------------------------------------------------------------- // - GetStatusEndpointStatus - Method to do the GET_STATUS command for the // endpoint that the IOUSBPipe is connected to. // [PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::GetStatusEndpointStatus( IOUSBPipe * thePipe, void * endpointStatus, IOUSBCompletion * completion ) { IOReturn status; if ( ( fTerminating == true ) || ( thePipe == NULL ) ) { // We're terminating, the device has probably been removed. // Nothing else to do except to report an error. return kIOReturnDeviceError; } // Clear out the structure for the request bzero( &fUSBDeviceRequest, sizeof(IOUSBDevRequest)); // Build the USB command fUSBDeviceRequest.bmRequestType = USBmakebmRequestType( kUSBIn, kUSBStandard, kUSBEndpoint); fUSBDeviceRequest.bRequest = kUSBRqGetStatus; fUSBDeviceRequest.wValue = 0; // Zero is EndpointStall fUSBDeviceRequest.wIndex = thePipe->GetEndpointNumber(); if ( thePipe == GetBulkInPipe() ) { fUSBDeviceRequest.wIndex |= 0x80; } fUSBDeviceRequest.wLength = 2; fUSBDeviceRequest.pData = endpointStatus; // Send the command over the control endpoint status = GetInterfaceReference()->DeviceRequest( &fUSBDeviceRequest, completion ); STATUS_LOG(( 5, "%s[%p]: GetStatusEndpointStatus returned %x", getName(), this, status )); return status; } #pragma mark - #pragma mark *** Accessor Methods For All Protocol Variables *** #pragma mark - /* The following methods are for use only by this class */ //----------------------------------------------------------------------------- // - GetInterfaceReference - Method to do the GET_STATUS command for the // endpoint that the IOUSBPipe is connected to. // [PROTECTED] //----------------------------------------------------------------------------- IOUSBInterface * IOUSBMassStorageClass::GetInterfaceReference( void ) { // Making this a 7 since it gets called A LOT. STATUS_LOG(( 7, "%s[%p]: GetInterfaceReference", getName(), this )); if ( fInterface == NULL ) { STATUS_LOG(( 2, "%s[%p]: GetInterfaceReference - Interface is NULL.", getName(), this )); } return fInterface; } //----------------------------------------------------------------------------- // - SetInterfaceReference [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::SetInterfaceReference( IOUSBInterface * newInterface ) { fInterface = newInterface; } //----------------------------------------------------------------------------- // - GetInterfaceSubClass [PROTECTED] //----------------------------------------------------------------------------- UInt8 IOUSBMassStorageClass::GetInterfaceSubclass( void ) { return fPreferredSubclass; } //----------------------------------------------------------------------------- // - GetInterfaceProtocol [PROTECTED] //----------------------------------------------------------------------------- UInt8 IOUSBMassStorageClass::GetInterfaceProtocol( void ) { return fPreferredProtocol; } //----------------------------------------------------------------------------- // - GetControlPipe [PROTECTED] //----------------------------------------------------------------------------- IOUSBPipe * IOUSBMassStorageClass::GetControlPipe( void ) { if ( fTerminating == true ) { return NULL; } return GetInterfaceReference()->GetDevice()->GetPipeZero(); } //----------------------------------------------------------------------------- // - GetBulkInPipe [PROTECTED] //----------------------------------------------------------------------------- IOUSBPipe * IOUSBMassStorageClass::GetBulkInPipe( void ) { return fBulkInPipe; } //----------------------------------------------------------------------------- // - GetBulkOutPipe [PROTECTED] //----------------------------------------------------------------------------- IOUSBPipe * IOUSBMassStorageClass::GetBulkOutPipe( void ) { return fBulkOutPipe; } //----------------------------------------------------------------------------- // - GetInterruptPipe [PROTECTED] //----------------------------------------------------------------------------- IOUSBPipe * IOUSBMassStorageClass::GetInterruptPipe( void ) { return fInterruptPipe; } //----------------------------------------------------------------------------- // - GetMaxLogicalUnitNumber [PROTECTED] //----------------------------------------------------------------------------- UInt8 IOUSBMassStorageClass::GetMaxLogicalUnitNumber( void ) const { return fMaxLogicalUnitNumber; } //----------------------------------------------------------------------------- // - SetMaxLogicalUnitNumber [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::SetMaxLogicalUnitNumber( UInt8 maxLUN ) { fMaxLogicalUnitNumber = maxLUN; } #pragma mark - #pragma mark *** Accessor Methods For CBI Protocol Variables *** #pragma mark - //----------------------------------------------------------------------------- // - GetCBIRequestBlock [PROTECTED] //----------------------------------------------------------------------------- CBIRequestBlock * IOUSBMassStorageClass::GetCBIRequestBlock( void ) { // Return a pointer to the CBIRequestBlock return &fCBICommandRequestBlock; } //----------------------------------------------------------------------------- // - ReleaseCBIRequestBlock [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::ReleaseCBIRequestBlock( CBIRequestBlock * cbiRequestBlock ) { // Clear the request and completion to avoid possible double callbacks. cbiRequestBlock->request = NULL; // Since we only allow one command and the CBIRequestBlock is // a member variable, no need to do anything. return; } #pragma mark - #pragma mark *** Accessor Methods For Bulk Only Protocol Variables *** #pragma mark - //----------------------------------------------------------------------------- // - GetBulkOnlyRequestBlock [PROTECTED] //----------------------------------------------------------------------------- BulkOnlyRequestBlock * IOUSBMassStorageClass::GetBulkOnlyRequestBlock( void ) { // Return a pointer to the BulkOnlyRequestBlock return &fBulkOnlyCommandRequestBlock; } //----------------------------------------------------------------------------- // - ReleaseBulkOnlyRequestBlock [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::ReleaseBulkOnlyRequestBlock( BulkOnlyRequestBlock * boRequestBlock ) { // Clear the request and completion to avoid possible double callbacks. boRequestBlock->request = NULL; // Since we only allow one command and the BulkOnlyRequestBlock is // a member variable, no need to do anything. return; } //----------------------------------------------------------------------------- // - GetNextBulkOnlyCommandTag [PROTECTED] //----------------------------------------------------------------------------- UInt32 IOUSBMassStorageClass::GetNextBulkOnlyCommandTag( void ) { fBulkOnlyCommandTag++; return fBulkOnlyCommandTag; } //----------------------------------------------------------------------------- // - HandlePowerOn - Will get called when a device has been resumed [PUBLIC] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::HandlePowerOn( void ) { UInt8 eStatus[2]; bool knownResetOnResumeDevice = false; // The USB hub port that the device is connected to has been resumed, // check to see if the device is still responding correctly and if not, // fix it so that it is. STATUS_LOG(( 6, "%s[%p]: HandlePowerOn", getName(), this )); if ( getProperty( kIOUSBMassStorageCharacteristics ) != NULL ) { OSDictionary * characterDict = OSDynamicCast( OSDictionary, getProperty( kIOUSBMassStorageCharacteristics )); // Check if this device is known to have problems when waking from sleep if ( characterDict->getObject( kIOUSBMassStorageResetOnResume ) != NULL ) { STATUS_LOG(( 4, "%s[%p]: knownResetOnResumeDevice", getName(), this )); knownResetOnResumeDevice = true; } } if ( ( GetStatusEndpointStatus( GetBulkInPipe(), &eStatus[0], NULL) != kIOReturnSuccess ) || ( knownResetOnResumeDevice == true ) ) { ResetDeviceNow( true ); } return kIOReturnSuccess; } //----------------------------------------------------------------------------- // - handleOpen [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::handleOpen( IOService * client, IOOptionBits options, void * arg ) { bool result = false; // If this is a normal open on a single LUN device. if ( GetMaxLogicalUnitNumber() == 0 ) { result = super::handleOpen ( client, options, arg ); goto Exit; } // It's an open from a multi-LUN client require_nonzero ( fClients, ErrorExit ); require_nonzero ( OSDynamicCast ( IOSCSILogicalUnitNub, client ), ErrorExit ); result = fClients->setObject ( client ); Exit: ErrorExit: return result; } //----------------------------------------------------------------------------- // - handleClose [PUBLIC] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::handleClose( IOService * client, IOOptionBits options ) { if ( GetMaxLogicalUnitNumber() == 0 ) { super::handleClose( client, options ); return; } require_nonzero ( fClients, Exit ); if ( fClients->containsObject( client ) ) { fClients->removeObject( client ); if ( ( fClients->getCount( ) == 0 ) && isInactive( ) ) { message( kIOMessageServiceIsRequestingClose, getProvider( ), 0 ); } } Exit: return; } //----------------------------------------------------------------------------- // - handleIsOpen [PUBLIC] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::handleIsOpen( const IOService * client ) const { bool result = false; UInt8 lun = GetMaxLogicalUnitNumber(); require_nonzero ( lun, CallSuperClass ); require_nonzero ( fClients, CallSuperClass ); // General case (is anybody open) if ( ( client == NULL ) && ( fClients->getCount ( ) != 0 ) ) { result = true; } else { // specific case (is this client open) result = fClients->containsObject ( client ); } return result; CallSuperClass: result = super::handleIsOpen ( client ); return result; } //----------------------------------------------------------------------------- // - sWaitForReset [STATIC][PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::sWaitForReset( void * refcon ) { return (( IOUSBMassStorageClass * ) refcon )->GatedWaitForReset(); } //----------------------------------------------------------------------------- // - GatedWaitForReset [PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::GatedWaitForReset( void ) { IOReturn status = kIOReturnSuccess; while ( fResetInProgress == true ) { status = fCommandGate->commandSleep( &fResetInProgress, THREAD_UNINT ); } return status; } //----------------------------------------------------------------------------- // - sWaitForTaskAbort [STATIC][PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::sWaitForTaskAbort( void * refcon ) { return (( IOUSBMassStorageClass * ) refcon )->GatedWaitForTaskAbort(); } //----------------------------------------------------------------------------- // - GatedWaitForTaskAbort [PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::GatedWaitForTaskAbort( void ) { IOReturn status = kIOReturnSuccess; while ( fAbortCurrentSCSITaskInProgress == true ) { status = fCommandGate->commandSleep( &fAbortCurrentSCSITaskInProgress, THREAD_UNINT ); } return status; } //----------------------------------------------------------------------------- // - sResetDevice [STATIC][PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::sResetDevice( void * refcon ) { IOUSBMassStorageClass * driver; IOReturn status = kIOReturnError; IOUSBInterface * interfaceRef = NULL; IOUSBDevice * deviceRef = NULL; driver = ( IOUSBMassStorageClass * ) refcon; STATUS_LOG(( 4, "%s[%p]: sResetDevice", driver->getName(), driver )); // Check if we should bail out because we are // being terminated. if ( ( driver->fTerminating == true ) || ( driver->isInactive( ) == true ) ) { STATUS_LOG(( 2, "%s[%p]: sResetDevice - We are being terminated!", driver->getName(), driver )); goto ErrorExit; } interfaceRef = driver->GetInterfaceReference(); require ( ( interfaceRef != NULL ), ErrorExit ); deviceRef = interfaceRef->GetDevice(); require ( ( deviceRef != NULL ), ErrorExit ); // Are we still connected to the hub? We only need to check this for full and low speed devices. status = deviceRef->message ( kIOUSBMessageHubIsDeviceConnected, NULL, 0 ); STATUS_LOG(( 5, "%s[%p]: kIOUSBMessageHubIsDeviceConnected returned = %x", driver->getName(), driver, status )); if ( status != kIOReturnNoDevice ) { // Device is still attached. Lets try resetting. status = deviceRef->ResetDevice(); STATUS_LOG(( 5, "%s[%p]: ResetDevice() returned = %x", driver->getName(), driver, status )); } if ( status != kIOReturnSuccess ) { // Device reset failed, or the device has been disconnected. // Were we attempting to recover the device after a major device error? if ( driver->fWaitingForReconfigurationMessage == true ) { // Yes, so we need to abort the command we'll not be able to retry. driver->fWaitingForReconfigurationMessage = false; } goto ErrorExit; } // We may get terminated during the call to ResetDevice() since it is synchronous. We should // check again whether we should bail or not. if ( ( driver->fTerminating == true ) || ( driver->isInactive( ) == true ) ) { STATUS_LOG(( 2, "%s[%p]: sResetDevice - We are being terminated (ii) !", driver->getName(), driver )); goto ErrorExit; } // Once the device has been reset, send notification to the client so that the // device can be reconfigured for use. driver->SendNotification_VerifyDeviceState(); ErrorExit: if ( status != kIOReturnSuccess ) { // We set the device state to detached so the proper status for the // device is returned along with the aborted SCSITask. driver->fDeviceAttached = false; // We complete the failed I/O with kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE // and either kSCSITaskStatus_DeviceNotPresent or kSCSITaskStatus_DeliveryFailure. driver->AbortCurrentSCSITask(); // Let the clients know that the device is gone. driver->SendNotification_DeviceRemoved( ); } if ( ( driver->fTerminating == true ) && ( driver->GetInterfaceReference() != NULL ) ) { IOUSBInterface * currentInterface; currentInterface = driver->GetInterfaceReference(); // We set this to NULL first chance we get to prevent a potential race between // our reset thread and our didTeriminate(). driver->SetInterfaceReference( NULL ); // Close and nullify our USB Interface. currentInterface->close( driver ); } driver->fResetInProgress = false; if ( driver->fWaitingForReconfigurationMessage == false ) { // Unblock our main thread. driver->fCommandGate->commandWakeup( &driver->fResetInProgress, false ); } STATUS_LOG(( 6, "%s[%p]: sResetDevice exiting.", driver->getName(), driver )); // We retained the driver in ResetDeviceNow() when // we created a thread for sResetDevice(). driver->release(); return; } //----------------------------------------------------------------------------- // - sAbortCurrentSCSITask [STATIC][PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::sAbortCurrentSCSITask( void * refcon ) { IOUSBMassStorageClass * driver; SCSITaskIdentifier currentTask = NULL; driver = ( IOUSBMassStorageClass * ) refcon; STATUS_LOG(( 4, "%s[%p]: sAbortCurrentSCSITask", driver->getName(), driver )); if( driver->fBulkOnlyCommandStructInUse == true ) { currentTask = driver->fBulkOnlyCommandRequestBlock.request; } else if( driver->fCBICommandStructInUse == true ) { currentTask = driver->fCBICommandRequestBlock.request; } if ( currentTask != NULL ) { if ( driver->fDeviceAttached == false ) { STATUS_LOG(( 1, "%s[%p]: sAbortCurrentSCSITask Aborting current SCSITask with device not present.", driver->getName(), driver )); driver->CommandCompleted( currentTask, kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE, kSCSITaskStatus_DeviceNotPresent ); } else { STATUS_LOG(( 1, "%s[%p]: sAbortCurrentSCSITask Aborting current SCSITask with delivery failure.", driver->getName(), driver )); driver->CommandCompleted( currentTask, kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE, kSCSITaskStatus_DeliveryFailure ); } } driver->fBulkOnlyCommandStructInUse = false; driver->fCBICommandStructInUse = false; driver->fAbortCurrentSCSITaskInProgress = false; driver->fCommandGate->commandWakeup( &driver->fAbortCurrentSCSITaskInProgress, false ); return; } OSMetaClassDefineReservedUsed( IOUSBMassStorageClass, 1 ); //----------------------------------------------------------------------------- // - StartDeviceRecovery - The recovery sequence to restore functionality for // devices that stop responding (like many devices // after a Suspend/Resume). [PROTECTED] //----------------------------------------------------------------------------- IOReturn IOUSBMassStorageClass::StartDeviceRecovery( void ) { // First check to see if the device is still connected. UInt8 eStatus[2]; IOReturn status = kIOReturnError; // The USB hub port that the device is connected to has been resumed, // check to see if the device is still responding correctly and if not, // fix it so that it is. STATUS_LOG(( 5, "%s[%p]: StartDeviceRecovery", getName(), this )); if( fBulkOnlyCommandStructInUse == true ) { // Set up the IOUSBCompletion structure fBulkOnlyCommandRequestBlock.boCompletion.target = this; fBulkOnlyCommandRequestBlock.boCompletion.action = &this->DeviceRecoveryCompletionAction; status = GetStatusEndpointStatus( GetBulkInPipe(), &eStatus[0], &fBulkOnlyCommandRequestBlock.boCompletion); } else if( fCBICommandStructInUse == true ) { // Set up the IOUSBCompletion structure fCBICommandRequestBlock.cbiCompletion.target = this; fCBICommandRequestBlock.cbiCompletion.action = &this->DeviceRecoveryCompletionAction; status = GetStatusEndpointStatus( GetBulkInPipe(), &eStatus[0], &fCBICommandRequestBlock.cbiCompletion); } return status; } OSMetaClassDefineReservedUsed( IOUSBMassStorageClass, 2 ); //----------------------------------------------------------------------------- // - FinishDeviceRecovery [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::FinishDeviceRecovery( IOReturn status ) { SCSITaskIdentifier tempTask = NULL; STATUS_LOG(( 4, "%s[%p]: + IOUSBMassStorageClass::FinishDeviceRecovery. Status = %x", getName(), this, status )); if( fBulkOnlyCommandStructInUse == true ) { tempTask = fBulkOnlyCommandRequestBlock.request; } else if ( fCBICommandStructInUse == true ) { tempTask = fCBICommandRequestBlock.request; } if ( ( fTerminating == true ) || isInactive() ) { // We're being terminated. Abort the outstanding command so the system can clean up. fDeviceAttached = false; goto ErrorExit; } if ( status != kIOReturnSuccess) { // The endpoint status could not be retrieved meaning that the device has // stopped responding. Begin the device reset sequence. STATUS_LOG(( 4, "%s[%p]: FinishDeviceRecovery reseting device on separate thread.", getName(), this )); ResetDeviceNow( false ); } // If the device is responding correctly or has been reset, retry the command. if ( status == kIOReturnSuccess ) { // Once the device has been reset, send notification to the client so that the // device can be reconfigured for use. SendNotification_VerifyDeviceState(); if( fBulkOnlyCommandStructInUse == true ) { STATUS_LOG(( 6, "%s[%p]: FinishDeviceRecovery SendSCSICommandforBulkOnlyProtocol sent", getName(), this )); status = SendSCSICommandForBulkOnlyProtocol( tempTask ); STATUS_LOG(( 5, "%s[%p]: FinishDeviceRecovery SendSCSICommandforBulkOnlyProtocol returned %x", getName(), this, status)); // SendSCSICommandForBulkOnlyProtocol clears out fBulkOnlyCommandRequestBlock.request if the command fails // to be sent correctly. We need to reset this value so we can abort the command. fBulkOnlyCommandRequestBlock.request = tempTask; require ( ( status == kIOReturnSuccess ), ErrorExit ); } else if ( fCBICommandStructInUse == true ) { STATUS_LOG(( 6, "%s[%p]: FinishDeviceRecovery SendSCSICommandforCBIProtocol sent", getName(), this )); status = SendSCSICommandForCBIProtocol( tempTask ); STATUS_LOG(( 5, "%s[%p]: FinishDeviceRecovery SendSCSICommandforCBIProtocol returned %x", getName(), this, status)); // SendSCSICommandForCBIProtocol clears out fBulkOnlyCommandRequestBlock.request if the command fails // to be sent correctly. We need to reset this value so we can abort the command. fCBICommandRequestBlock.request = tempTask; require ( ( status == kIOReturnSuccess ), ErrorExit ); } } STATUS_LOG(( 4, "%s[%p]: - IOUSBMassStorageClass::FinishDeviceRecovery", getName(), this )); return; ErrorExit: if ( tempTask != NULL ) { AbortCurrentSCSITask(); } STATUS_LOG(( 4, "%s[%p]: - IOUSBMassStorageClass::FinishDeviceRecovery - AbortCurrentSCSITask", getName(), this )); } //----------------------------------------------------------------------------- // - DeviceRecoveryCompletionAction [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::DeviceRecoveryCompletionAction( void * target, void * parameter, IOReturn status, UInt32 bufferSizeRemaining) { UNUSED( parameter ); UNUSED( bufferSizeRemaining ); IOUSBMassStorageClass * theMSC; theMSC = (IOUSBMassStorageClass *) target; theMSC->FinishDeviceRecovery( status ); } //----------------------------------------------------------------------------- // - ResetDeviceNow [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::ResetDeviceNow( bool waitForReset ) { // Make sure we aren't terminating. if ( ( fTerminating == false ) && ( isInactive() == false ) ) { // We call retain here so that the driver will stick around long enough for // sResetDevice() to do it's thing in case we are being terminated. The // retain() is balanced with a release in sResetDevice(). retain(); // The endpoint status could not be retrieved meaning that the device has // stopped responding. Or this could be a device we know needs a reset. // Begin the device reset sequence. STATUS_LOG(( 4, "%s[%p]: ResetDeviceNow", getName(), this )); // Reset the device on its own thread so we don't deadlock. fResetInProgress = true; fWaitingForReconfigurationMessage = false; // If we aren't going to block on the reset thread, we need to wait for the // reconfiguration message. if ( waitForReset == false ) { fWaitingForReconfigurationMessage = true; } IOCreateThread( IOUSBMassStorageClass::sResetDevice, this ); if ( waitForReset == true ) { STATUS_LOG(( 4, "%s[%p]: kIOMessageServiceIsResumed Waiting for Reset.", getName(), this )); fCommandGate->runAction ( ( IOCommandGate::Action ) &IOUSBMassStorageClass::sWaitForReset ); } } } //----------------------------------------------------------------------------- // - AbortCurrentSCSITask [PROTECTED] //----------------------------------------------------------------------------- void IOUSBMassStorageClass::AbortCurrentSCSITask( void ) { // We call retain here so that the driver will stick around long enough for // sAbortCurrentSCSITask() to do it's thing in case we are being terminated. retain(); // The endpoint status could not be retrieved meaning that the device has // stopped responding. Or this could be a device we know needs a reset. // Begin the device reset sequence. STATUS_LOG(( 4, "%s[%p]: AbortCurrentSCSITask called!", getName(), this )); // Abort the SCSITask on a separate thread so we don't deadlock. fAbortCurrentSCSITaskInProgress = true; IOCreateThread( IOUSBMassStorageClass::sAbortCurrentSCSITask, this ); fCommandGate->runAction ( ( IOCommandGate::Action ) &IOUSBMassStorageClass::sWaitForTaskAbort ); Exit: // We retained ourselves earlier in this method, time to balance that out. release(); STATUS_LOG(( 4, "%s[%p]: AbortCurrentSCSITask Exiting", getName(), this )); } //----------------------------------------------------------------------------- // - IsPhysicalInterconnectLocationInternal [PROTECTED] //----------------------------------------------------------------------------- bool IOUSBMassStorageClass::IsPhysicalInterconnectLocationInternal ( void ) { IOReturn status = kIOReturnError; IOUSBInterface * usbInterface = NULL; IOUSBDevice * usbDevice = NULL; UInt32 deviceInformation = 0; bool internal = false; // We acquire our references individually to avoid panics. // Get a reference to our USB interface. usbInterface = GetInterfaceReference(); require ( ( usbInterface != NULL ), ErrorExit ); // Get a reference to our USB device. usbDevice = usbInterface->GetDevice(); require ( ( usbDevice != NULL ), ErrorExit ); status = usbDevice->GetDeviceInformation ( &deviceInformation ); require_success ( status, ErrorExit ); if ( deviceInformation & ( 1 << kUSBDeviceInfoIsInternalMask ) ) { internal = true; } ErrorExit: return status; } // Space reserved for future expansion. OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 3 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 4 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 5 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 6 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 7 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 8 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 9 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 10 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 11 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 12 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 13 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 14 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 15 ); OSMetaClassDefineReservedUnused( IOUSBMassStorageClass, 16 );