/* * Copyright (c) 1998-2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * 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@ */ #include #include #include #include #include "IOATABlockStorageDriver.h" #define ATA_BLOCK_STORAGE_DRIVER_DEBUGGING_LEVEL 0 #if ( ATA_BLOCK_STORAGE_DRIVER_DEBUGGING_LEVEL >= 1 ) #define PANIC_NOW(x) IOPanic x #else #define PANIC_NOW(x) #endif #if ( ATA_BLOCK_STORAGE_DRIVER_DEBUGGING_LEVEL >= 2 ) #define ERROR_LOG(x) IOLog x #else #define ERROR_LOG(x) #endif #if ( ATA_BLOCK_STORAGE_DRIVER_DEBUGGING_LEVEL >= 3 ) #define STATUS_LOG(x) IOLog x #else #define STATUS_LOG(x) #endif #if ( ATA_BLOCK_STORAGE_DRIVER_PM_DEBUGGING_LEVEL >= 1 ) #define SERIAL_STATUS_LOG(x) kprintf x #else #define SERIAL_STATUS_LOG(x) #endif // Media icon keys #define kCFBundleIdentifierKey "CFBundleIdentifier" #define kIOATABlockStorageIdentifierKey "com.apple.iokit.IOATABlockStorage" #define kPCCardIconFileKey "PCCard.icns" // Configuration state machine enum { kPIOTransferModeSetup = 1, kPIOTransferModeDone = 2, kDMATransferModeDone = 3, kReadAheadEnableDone = 4, kWriteCacheEnableDone = 5 }; struct ATAConfigData { IOATABlockStorageDriver * self; UInt32 state; IOSyncer * syncer; }; typedef struct ATAConfigData ATAConfigData; //-------------------------------------------------------------------------------------- // ¥ identifyATADevice - Sends a device identify request to the device // and uses it to configure the drive speeds //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::identifyATADevice ( void ) { IOReturn status = kIOReturnSuccess; IOATACommand * cmd = NULL; ATAClientData * clientData = NULL; STATUS_LOG ( ( "IOATABlockStorageDriver::identifyATADevice entering.\n" ) ); // Get a new command object cmd = getATACommandObject ( ); assert ( cmd != NULL ); clientData = ( ATAClientData * ) cmd->refCon; // Zero the command object cmd->zeroCommand ( ); // Start filling in the command cmd->setUnit ( fATAUnitID ); sSetCommandBuffers ( cmd, fDeviceIdentifyBuffer, 0, 1 ); cmd->setCommand ( kATAcmdDriveIdentify ); cmd->setFlags ( mATAFlagIORead ); cmd->setOpcode ( kATAFnExecIO ); // set the device head to the correct unit cmd->setDevice_Head ( fATAUnitID << 4 ); cmd->setRegMask ( ( ataRegMask ) ( mATAErrFeaturesValid | mATAStatusCmdValid ) ); // Save state data clientData->command = kATAcmdDriveIdentify; clientData->flags = mATAFlagIORead; clientData->opCode = kATAFnExecIO; clientData->regMask = ( ataRegMask ) ( mATAErrFeaturesValid | mATAStatusCmdValid ); STATUS_LOG ( ( "IOATABlockStorageDriver::identifyATADevice executing identify command.\n" ) ); status = syncExecute ( cmd, kATATimeout10Seconds ); if ( status != kIOReturnSuccess ) { ERROR_LOG ( ( "IOATABlockStorageDriver::identifyATADevice result = %ld.\n", ( UInt32 ) cmd->getResult ( ) ) ); return status; } // Validate the identify data before the swap! status = sValidateIdentifyData ( ( UInt8 * ) fDeviceIdentifyData ); #if defined(__BIG_ENDIAN__) // The identify device info needs to be byte-swapped on big-endian (ppc) // systems becuase it is data that is produced by the drive, read across a // 16-bit little-endian PCI interface, directly into a big-endian system. // Regular data doesn't need to be byte-swapped because it is written and // read from the host and is intrinsically byte-order correct. sSwapBytes16 ( ( UInt8 * ) fDeviceIdentifyData, 512 ); #endif STATUS_LOG ( ( "IOATABlockStorageDriver::identifyATADevice exiting with status = %ld.\n", ( UInt32 ) status ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ setupReadWriteTaskFile - Setup the command's taskfile registers based on // parameters given // block - Initial transfer block // nblks - Number of blocks to transfer //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::setupReadWriteTaskFile ( IOATACommand * cmd, IOMemoryDescriptor * buffer, UInt32 block, UInt32 nblks ) { ATAClientData * clientData; UInt32 flags = mATAFlagUseConfigSpeed; UInt8 command = 0; bool isWrite = ( buffer->getDirection ( ) == kIODirectionOut ); STATUS_LOG ( ( "IOATABlockStorageDriver::setupReadWriteTaskFile entering.\n" ) ); clientData = ( ATAClientData * ) cmd->refCon; // Zero the command cmd->zeroCommand ( ); // Check if we are capable of DMA for the I/O if ( fUltraDMAMode || fDMAMode ) { if ( fUseExtendedLBA ) { command = ( isWrite ) ? kATAcmdWriteDMAExtended : kATAcmdReadDMAExtended; flags |= mATAFlag48BitLBA; } else { command = ( isWrite ) ? kATAcmdWriteDMA : kATAcmdReadDMA; } flags |= mATAFlagUseDMA; } else { if ( fUseExtendedLBA ) { command = ( isWrite ) ? kATAcmdWriteExtended : kATAcmdReadExtended; } else { command = ( isWrite ) ? kATAcmdWrite : kATAcmdRead; } } flags |= ( isWrite ) ? mATAFlagIOWrite : mATAFlagIORead; // Set the ATA Command cmd->setCommand ( command ); cmd->setSectorCount ( ( nblks == kIOATAMaxBlocksPerXfer ) ? 0 : nblks ); if ( fUseLBAAddressing ) { if ( fUseExtendedLBA ) { IOExtendedLBA * extLBA = cmd->getExtendedLBA ( ); extLBA->setExtendedLBA ( 0, block, fATAUnitID, ( UInt16 ) nblks, command ); clientData->useExtendedLBA = true; clientData->lbaLow16 = extLBA->getLBALow16 ( ); clientData->lbaMid16 = extLBA->getLBAMid16 ( ); clientData->lbaHigh16 = extLBA->getLBAHigh16 ( ); clientData->sectorCount16 = extLBA->getSectorCount16 ( ); clientData->features16 = extLBA->getFeatures16 ( ); clientData->device = extLBA->getDevice ( ); clientData->command16 = extLBA->getCommand ( ); } else { STATUS_LOG ( ( "IOATABlockStorageDriver::setupReadWriteTaskFile block = %lx.\n", block ) ); // 28bit LBA addressing supported cmd->setLBA28 ( block, fATAUnitID ); } clientData->sectorNumber = ( block & 0xFF ); clientData->cylLow = ( ( block & 0xFF00 ) >> 8 ); clientData->cylHigh = ( ( block & 0x00FF0000 ) >> 16 ); } else { UInt32 heads = 0; UInt32 sectors = 0; heads = fDeviceIdentifyData[kATAIdentifyLogicalHeadCount]; sectors = fDeviceIdentifyData[kATAIdentifySectorsPerTrack]; // Not LBA, so use CHS addressing model sConvertBlockToCHSAddress ( cmd, block, heads, sectors, fATAUnitID ); } sSetCommandBuffers ( cmd, buffer, 0, nblks ); cmd->setUnit ( fATAUnitID ); cmd->setFlags ( flags ); cmd->setOpcode ( kATAFnExecIO ); clientData->command = command; clientData->opCode = kATAFnExecIO; clientData->flags = flags; STATUS_LOG ( ( "IOATABlockStorageDriver::setupReadWriteTaskFile exiting.\n" ) ); } //-------------------------------------------------------------------------------------- // ¥ ataCommandReadWrite - Setup the command for a read/write operation // // buffer - IOMemoryDescriptor object describing this transfer. // block - Initial transfer block. // nblks - Number of blocks to transfer. //-------------------------------------------------------------------------------------- IOATACommand * IOATABlockStorageDriver::ataCommandReadWrite ( IOMemoryDescriptor * buffer, UInt32 block, UInt32 nblks ) { IOATACommand * cmd = NULL; STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandReadWrite entering.\n" ) ); assert ( buffer != NULL ); cmd = getATACommandObject ( ); if ( cmd == NULL ) return NULL; // Setup the taskfile structure with the size and direction of the // transfer. This structure will be written to the actual taskfile // registers when this command is processed. setupReadWriteTaskFile ( cmd, buffer, block, nblks ); STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandReadWrite exiting.\n" ) ); return cmd; } //-------------------------------------------------------------------------------------- // ¥ ataCommandSetFeatures - Setup the command for a set features operation // // features - contents of the Features register // sectorCount - contents of Sector Count register // sectorNumber - contents of Sector Number register // cylinderLow - contents of the Cylinder Low register // cylinderHigh - contents of the Cylinder High register //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::ataCommandSetFeatures ( UInt8 features, UInt8 sectorCount, UInt8 sectorNumber, UInt8 cylinderLow, UInt8 cylinderHigh, UInt32 flags, bool forceSync ) { IOReturn status = kIOReturnSuccess; STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandSetFeatures entering.\n" ) ); // Zero the command fConfigurationCommand->zeroCommand ( ); fConfigurationCommand->setUnit ( fATAUnitID ); fConfigurationCommand->setFeatures ( features ); fConfigurationCommand->setSectorCount ( sectorCount ); fConfigurationCommand->setSectorNumber ( sectorNumber ); fConfigurationCommand->setOpcode ( kATAFnExecIO ); fConfigurationCommand->setCommand ( kATAcmdSetFeatures ); fConfigurationCommand->setDevice_Head ( fATAUnitID << 4 ); fConfigurationCommand->setCylHi ( cylinderHigh ); fConfigurationCommand->setCylLo ( cylinderLow ); fConfigurationCommand->setFlags ( flags ); fConfigurationCommand->setTimeoutMS ( kATATimeout10Seconds ); if ( forceSync ) { fConfigurationCommand->setCallbackPtr ( &IOATABlockStorageDriver::sATAVoidCallback ); } else { fConfigurationCommand->setCallbackPtr ( &IOATABlockStorageDriver::sATAConfigStateMachine ); } status = fATADevice->executeCommand ( fConfigurationCommand ); STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandSetFeatures exiting.\n" ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ ataCommandFlushCache - Setup the command for a flush cache operation //-------------------------------------------------------------------------------------- IOATACommand * IOATABlockStorageDriver::ataCommandFlushCache ( void ) { IOATACommand * cmd = NULL; ATAClientData * clientData = NULL; STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandFlushCache entering.\n" ) ); cmd = getATACommandObject ( ); if ( cmd == NULL ) return NULL; clientData = ( ATAClientData * ) cmd->refCon; // Zero the command cmd->zeroCommand ( ); if ( fDeviceIdentifyData[kATAIdentifyCommandSetSupported2] & kATASupportsFlushCacheExtendedMask ) { IOExtendedLBA * extLBA = cmd->getExtendedLBA ( ); extLBA->setExtendedLBA ( 0, 0, fATAUnitID, 0, kATAcmdFlushCacheExtended ); cmd->setFlags ( mATAFlag48BitLBA ); clientData->useExtendedLBA = true; clientData->lbaLow16 = extLBA->getLBALow16 ( ); clientData->lbaMid16 = extLBA->getLBAMid16 ( ); clientData->lbaHigh16 = extLBA->getLBAHigh16 ( ); clientData->sectorCount16 = extLBA->getSectorCount16 ( ); clientData->features16 = extLBA->getFeatures16 ( ); clientData->device = extLBA->getDevice ( ); clientData->command16 = extLBA->getCommand ( ); } cmd->setOpcode ( kATAFnExecIO ); cmd->setCommand ( kATAcmdFlushCache ); cmd->setDevice_Head ( fATAUnitID << 4 ); cmd->setUnit ( fATAUnitID ); clientData->command = kATAcmdFlushCache; clientData->opCode = kATAFnExecIO; STATUS_LOG ( ( "IOATABlockStorageDriver::ataCommandFlushCache exiting.\n" ) ); return cmd; } //--------------------------------------------------------------------------- // Issue a synchronous ATA command. //--------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::syncExecute ( IOATACommand * cmd, UInt32 timeout, UInt8 retries ) { ATAClientData * clientData = NULL; IOReturn status = kIOReturnSuccess; STATUS_LOG ( ( "IOATABlockStorageDriver::syncExecute entering.\n" ) ); clientData = ( ATAClientData * ) cmd->refCon; assert ( clientData != NULL ); cmd->setTimeoutMS ( timeout ); cmd->setCallbackPtr ( &IOATABlockStorageDriver::sHandleCommandCompletion ); // Set up for synchronous transaction clientData->completion.syncLock = IOSyncer::create ( ); assert ( clientData->completion.syncLock != NULL ); if ( clientData->completion.syncLock == NULL ) { returnATACommandObject ( cmd ); return kIOReturnNoResources; } // set up any client data necessary for the command clientData->isSync = true; clientData->self = this; clientData->maxRetries = retries; clientData->timeout = timeout; // save the state data in case we need to issue retries sSaveStateData ( cmd ); fATADevice->executeCommand ( cmd ); // Block client thread on lock until the completion handler // receives an indication that the processing is complete clientData->completion.syncLock->wait ( ); STATUS_LOG ( ( "IOATABlockStorageDriver::syncExecute exiting.\n" ) ); // Pull the error from the clientData status = clientData->returnCode; // Re-enqueue the command object since we're done with it returnATACommandObject ( cmd ); return status; } //--------------------------------------------------------------------------- // Issue an asynchronous ATA command. //--------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::asyncExecute ( IOATACommand * cmd, IOStorageCompletion completion, UInt32 timeout, UInt8 retries ) { ATAClientData * clientData = NULL; IOReturn status = kIOReturnSuccess; STATUS_LOG ( ( "IOATABlockStorageDriver::asyncExecute entering.\n" ) ); clientData = ( ATAClientData * ) cmd->refCon; assert ( clientData != NULL ); // Set timeout and register the completion handler. cmd->setTimeoutMS ( timeout ); cmd->setCallbackPtr ( &IOATABlockStorageDriver::sHandleCommandCompletion ); // Set up for an asynchronous transaction clientData->isSync = false; clientData->self = this; clientData->maxRetries = retries; clientData->completion.async = completion; clientData->timeout = timeout; // save the state data in case we need to issue retries sSaveStateData ( cmd ); // Execute the command status = fATADevice->executeCommand ( cmd ); if ( status != kATANoErr ) { bzero ( clientData, sizeof ( ATAClientData ) ); returnATACommandObject ( cmd ); } return status; } //-------------------------------------------------------------------------------------- // ¥ allocateATACommandObjects - allocates ATA command objects //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::allocateATACommandObjects ( void ) { STATUS_LOG ( ( "IOATABlockStorageDriver::allocateATACommandObjects entering\n" ) ); IOATACommand * cmd = NULL; ATAClientData * clientData = NULL; IOSyncer * syncer = NULL; ATAConfigData * configData = NULL; for ( UInt32 index = 0; index < fNumCommandObjects; index++ ) { // Allocate the command cmd = fATADevice->allocCommand ( ); assert ( cmd != NULL ); // Allocate the command clientData clientData = ( ATAClientData * ) IOMalloc ( sizeof ( ATAClientData ) ); assert ( clientData != NULL ); bzero ( clientData, sizeof ( ATAClientData ) ); // set the back pointers to each other cmd->refCon = ( void * ) clientData; clientData->cmd = cmd; STATUS_LOG ( ( "adding command to pool\n" ) ); // Enqueue the command in the free list fCommandPool->returnCommand ( cmd ); } // Allocate the command fResetCommand = fATADevice->allocCommand ( ); fPowerManagementCommand = fATADevice->allocCommand ( ); fConfigurationCommand = fATADevice->allocCommand ( ); assert ( fResetCommand != NULL ); assert ( fPowerManagementCommand != NULL ); assert ( fConfigurationCommand != NULL ); syncer = IOSyncer::create ( ); assert ( syncer != NULL ); fPowerManagementCommand->refCon = ( void * ) syncer; configData = ( ATAConfigData * ) IOMalloc ( sizeof ( ATAConfigData ) ); assert ( configData != NULL ); bzero ( configData, sizeof ( ATAConfigData ) ); configData->syncer = IOSyncer::create ( ); assert ( configData->syncer != NULL ); fConfigurationCommand->refCon = ( void * ) configData; STATUS_LOG ( ( "IOATABlockStorageDriver::allocateATACommandObjects exiting\n" ) ); } //-------------------------------------------------------------------------------------- // ¥ dellocateATACommandObjects - deallocates ATA command objects //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::deallocateATACommandObjects ( void ) { STATUS_LOG ( ( "IOATABlockStorageDriver::dellocateATACommandObjects entering\n" ) ); IOATACommand * cmd = NULL; ATAClientData * clientData = NULL; IOSyncer * syncer = NULL; ATAConfigData * configData = NULL; cmd = ( IOATACommand * ) fCommandPool->getCommand ( false ); assert ( cmd != NULL ); //¥¥¥ Walk the in-use queue and abort the commands (potential memory leak right now) // This handles walking the free command queue while ( cmd != NULL ) { clientData = ( ATAClientData * ) cmd->refCon; assert ( clientData != NULL ); IOFree ( clientData, sizeof ( ATAClientData ) ); clientData = NULL; fATADevice->freeCommand ( cmd ); cmd = ( IOATACommand * ) fCommandPool->getCommand ( false ); } syncer = ( IOSyncer * ) fPowerManagementCommand->refCon; syncer->release ( ); configData = ( ATAConfigData * ) fConfigurationCommand->refCon; configData->syncer->release ( ); IOFree ( configData, sizeof ( ATAConfigData ) ); fATADevice->freeCommand ( fResetCommand ); fATADevice->freeCommand ( fPowerManagementCommand ); fATADevice->freeCommand ( fConfigurationCommand ); STATUS_LOG ( ( "IOATABlockStorageDriver::dellocateATACommandObjects exiting.\n" ) ); } //-------------------------------------------------------------------------------------- // ¥ getATACommandObject - Gets the first non-busy ata command object // If they are all busy, it returns NULL //-------------------------------------------------------------------------------------- IOATACommand * IOATABlockStorageDriver::getATACommandObject ( bool blockForCommand ) { IOATACommand * cmd = NULL; STATUS_LOG ( ( "IOATABlockStorageDriver::getATACommandObject entering.\n" ) ); cmd = ( IOATACommand * ) fCommandPool->getCommand ( blockForCommand ); assert ( cmd != NULL ); return cmd; } //-------------------------------------------------------------------------------------- // ¥ returnATACommandObject - returns the ATA command object to the pool //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::returnATACommandObject ( IOATACommand * cmd ) { ATAClientData * clientData; STATUS_LOG ( ( "IOATABlockStorageDriver::returnATACommandObject entering.\n" ) ); assert ( cmd != NULL ); clientData = ( ATAClientData * ) cmd->refCon; assert ( clientData != NULL ); // Clear out the clientData, so we don't reuse any variables bzero ( clientData, sizeof ( ATAClientData ) ); // set the back pointers to each other cmd->refCon = ( void * ) clientData; clientData->cmd = cmd; // Finally, re-enqueue the command fCommandPool->returnCommand ( cmd ); } //-------------------------------------------------------------------------------------- // ¥ identifyAndConfigureATADevice - Sends a device identify request to the device // and uses it to configure the drive speeds //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::identifyAndConfigureATADevice ( void ) { IOReturn status = kIOReturnSuccess; IOATABusInfo * busInfoPtr = NULL; IOATADevConfig * deviceConfigPtr = NULL; UInt16 tempWord = 0; UInt16 maxBlocks = 0; UInt64 maxSize = 0; // Get some info about the ATA bus busInfoPtr = IOATABusInfo::atabusinfo ( ); if ( busInfoPtr == NULL ) return kIOReturnNoResources; // Zero the data, and then get the information from the ATA controller busInfoPtr->zeroData ( ); status = fATADevice->provideBusInfo ( busInfoPtr ); if ( status != kIOReturnSuccess ) { ERROR_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice provide bus info failed thErr = %ld.\n", ( UInt32 ) status ) ); goto ReleaseBusInfoAndBail; } // Get the socket type fATASocketType = busInfoPtr->getSocketType ( ); STATUS_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice socket type = %d.\n", ( UInt8 ) fATASocketType ) ); // Set the media icon to PC-Card if it is a PC Card device if ( fATASocketType == kPCCardSocket ) { OSDictionary * dict = OSDictionary::withCapacity ( 2 ); if ( dict != NULL ) { OSString * identifier = OSString::withCString ( kIOATABlockStorageIdentifierKey ); OSString * resourceFile = OSString::withCString ( kPCCardIconFileKey ); if ( ( identifier != NULL ) && ( resourceFile != NULL ) ) { dict->setObject ( kCFBundleIdentifierKey, identifier ); dict->setObject ( kIOBundleResourceFileKey, resourceFile ); setProperty ( kIOMediaIconKey, dict ); } if ( identifier != NULL ) identifier->release ( ); if ( resourceFile != NULL ) resourceFile->release ( ); dict->release ( ); } } // Execute an ATA device identify status = identifyATADevice ( ); if ( status != kIOReturnSuccess ) { goto ReleaseBusInfoAndBail; } // Parse the identify data tempWord = fDeviceIdentifyData[kATAIdentifyCommandSetSupported]; // Check for S.M.A.R.T. if ( ( tempWord & kATASupportsSMARTMask ) == kATASupportsSMARTMask ) fSupportedFeatures |= kIOATAFeatureSMART; // Check for power management if ( ( tempWord & kATASupportsPowerManagementMask ) == kATASupportsPowerManagementMask ) fSupportedFeatures |= kIOATAFeaturePowerManagement; // Check for write cache if ( ( tempWord & kATASupportsWriteCacheMask ) == kATASupportsWriteCacheMask ) fSupportedFeatures |= kIOATAFeatureWriteCache; tempWord = fDeviceIdentifyData[kATAIdentifyCommandSetSupported2]; if ( ( tempWord & kATADataIsValidMask ) == 0x4000 ) { // Check for APM if ( ( tempWord & kATASupportsAdvancedPowerManagementMask ) == kATASupportsAdvancedPowerManagementMask ) fSupportedFeatures |= kIOATAFeatureAdvancedPowerManagement; // Check for CF if ( ( tempWord & kATASupportsCompactFlashMask ) == kATASupportsCompactFlashMask ) fSupportedFeatures |= kIOATAFeatureCompactFlash; } // Sanity check. CHS is rarely used, but it is still good to do things correctly. if ( fDeviceIdentifyData[kATAIdentifyDriveCapabilities] & kLBASupportedMask ) fUseLBAAddressing = true; // Check for ExtendedLBA (48-bit) support from the bus. fUseExtendedLBA = busInfoPtr->supportsExtendedLBA ( ); // Mask with drive support fUseExtendedLBA &= IOATADevConfig::sDriveSupports48BitLBA ( fDeviceIdentifyData ); if ( fUseExtendedLBA ) { maxBlocks = busInfoPtr->maxBlocksExtended ( ); // 48-bit LBA supported fSupportedFeatures |= kIOATAFeature48BitLBA; } else { // Not using 48-bit LBA, so use normal constraints maxBlocks = kIOATAMaximumBlockCount8Bit; } maxSize = ( UInt64 ) maxBlocks * ( UInt64 ) kATADefaultSectorSize; // Publish some constraints setProperty ( kIOMaximumBlockCountReadKey, maxBlocks, 64 ); setProperty ( kIOMaximumBlockCountWriteKey, maxBlocks, 64 ); setProperty ( kIOMaximumSegmentCountReadKey, maxSize / PAGE_SIZE, 64 ); setProperty ( kIOMaximumSegmentCountWriteKey, maxSize / PAGE_SIZE, 64 ); deviceConfigPtr = IOATADevConfig::atadevconfig ( ); assert ( deviceConfigPtr != NULL ); // Get the device config from ATA Controller status = fATADevice->provideConfig ( deviceConfigPtr ); if ( status != kIOReturnSuccess ) { ERROR_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice provideConfig returned an error = %ld.\n", ( UInt32 ) status ) ); goto ReleaseBusInfoAndBail; } // Pass the bus information and device identify info to have ATA Controller choose // the best config for this device (if it can) status = deviceConfigPtr->initWithBestSelection ( fDeviceIdentifyData, busInfoPtr ); if ( status != kIOReturnSuccess ) { goto ReleaseBusInfoAndBail; } // Select the config it gives us status = fATADevice->selectConfig ( deviceConfigPtr ); if ( status != kIOReturnSuccess ) { ERROR_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice selectConfig returned error = %ld.\n", ( UInt32 ) status ) ); } // Store the PIOModes, DMAModes and UltraDMAModes supported fPIOMode = deviceConfigPtr->getPIOMode ( ); fDMAMode = deviceConfigPtr->getDMAMode ( ); fUltraDMAMode = deviceConfigPtr->getUltraMode ( ); // Add any more configuration info we need (write cache enable, power management, etc.) status = configureATADevice ( ); STATUS_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice configureATADevice returned status = %ld.\n", ( UInt32 ) status ) ); ReleaseBusInfoAndBail: if ( busInfoPtr != NULL ) { STATUS_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice releasing bus info.\n" ) ); busInfoPtr->release ( ); busInfoPtr = NULL; } if ( deviceConfigPtr != NULL ) { STATUS_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice releasing device config.\n" ) ); deviceConfigPtr->release ( ); deviceConfigPtr = NULL; } STATUS_LOG ( ( "IOATABlockStorageDriver::identifyAndConfigureATADevice returning status = %ld.\n", ( UInt32 ) status ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ configureATADevice - Configures the ATA Device //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::configureATADevice ( void ) { ATAConfigData * configData; STATUS_LOG ( ( "IOATABlockStorageDriver::configureATADevice entering.\n" ) ); configData = ( ATAConfigData * ) fConfigurationCommand->refCon; configData->self = this; configData->state = kPIOTransferModeSetup; sATAConfigStateMachine ( fConfigurationCommand ); configData->syncer->reinit ( ); configData->syncer->wait ( false ); return kIOReturnSuccess; } //-------------------------------------------------------------------------------------- // ¥ reconfigureATADevice - Reconfigures the ATA Device //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::reconfigureATADevice ( void ) { // There is a window here on a machine with both device0 and device1 ATA // devices where if a device is not ready and a bus reset occurs, and the // other driver hasn't allocated resources yet but is instantiated, it // could cause a panic. We check to make sure the fConfigurationCommand // instance variable is not NULL to make sure all resources necessary // for reconfiguration have been allocated before proceeding with // the reconfiguration. if ( fConfigurationCommand != NULL ) { // We are on a callout from the ATA controller and need to execute // all of these commands synchronously. setPIOTransferMode ( true ); setDMATransferMode ( true ); // Check if a client has previously set the APM level. If so, // reconfigure the device with that value. if ( fAPMLevel != 0xFF ) { setAdvancedPowerManagementLevel ( fAPMLevel, true ); } ataCommandSetFeatures ( kATAEnableReadAhead, 0, 0, 0, 0, mATAFlagImmediate, true ); ataCommandSetFeatures ( kATAEnableWriteCache, 0, 0, 0, 0, mATAFlagImmediate, true ); } return kIOReturnSuccess; } //-------------------------------------------------------------------------------------- // ¥ setPIOTransferMode - Configures the ATA Device's PIO Transfer mode //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::setPIOTransferMode ( bool forceSync ) { IOReturn status = kIOReturnSuccess; UInt8 mode = 0; STATUS_LOG ( ( "IOATABlockStorageDriver::setPIOTransferMode entering.\n" ) ); // Always set to highest transfer mode mode = sConvertHighestBitToNumber ( fPIOMode ); // PIO transfer mode is capped at 4 for now in the ATA-5 spec. If a device supports // more than mode 4 it has to at least support mode 4. We might not get the best // performance out of the drive, but it will work until we update to latest spec. if ( mode > 4 ) { STATUS_LOG ( ( "IOATABlockStorageDriver::setPIOTransferMode mode > 4 = %ld.\n", ( UInt32 ) mode ) ); mode = 4; } status = ataCommandSetFeatures ( kATASetTransferMode, ( kATAEnablePIOModeMask | mode ), 0, 0, 0, mATAFlagImmediate, forceSync ); STATUS_LOG ( ( "IOATABlockStorageDriver::setPIOTransferMode exiting with error = %ld.\n", ( UInt32 ) status ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ setDMATransferMode - Configures the ATA Device's DMA Transfer mode //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::setDMATransferMode ( bool forceSync ) { IOReturn status = kIOReturnSuccess; UInt8 mode = 0; UInt8 sectorCount = 0; STATUS_LOG ( ( "IOATABlockStorageDriver::SetDMATransferMode entering.\n" ) ); // Always set to highest transfer mode if ( fUltraDMAMode ) { // Device supports UltraDMA STATUS_LOG ( ( "IOATABlockStorageDriver::SetDMATransferMode choosing UltraDMA.\n" ) ); mode = sConvertHighestBitToNumber ( fUltraDMAMode ); // Ultra DMA is capped at 8 for now in the ATA-5 spec. If a device supports // more than mode 8 it MUST at least support mode 8. if ( mode > 8 ) mode = 8; sectorCount = ( kATAEnableUltraDMAModeMask | mode ); } else { STATUS_LOG ( ( "IOATABlockStorageDriver::SetDMATransferMode choosing DMA.\n" ) ); mode = sConvertHighestBitToNumber ( fDMAMode ); // MultiWord DMA is capped at 2 for now in the ATA-5 spec. If a device supports // more than mode 2 it MUST at least support mode 2. We might not get the best // performance out of the drive, but it will work until we update to latest spec. if ( mode > 2 ) mode = 2; sectorCount = ( kATAEnableMultiWordDMAModeMask | mode ); } status = ataCommandSetFeatures ( kATASetTransferMode, sectorCount, 0, 0, 0, mATAFlagImmediate, forceSync ); STATUS_LOG ( ( "IOATABlockStorageDriver::SetDMATransferMode exiting with error = %ld.\n", ( UInt32 ) status ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ setAdvancedPowerManagementLevel - Configures the ATA Device's APM Level mode //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::setAdvancedPowerManagementLevel ( UInt8 level, bool forceSync ) { IOReturn status = kIOReturnSuccess; STATUS_LOG ( ( "IOATABlockStorageDriver::setAdvancedPowerManagementLevel entering.\n" ) ); fAPMLevel = level; // First, see if we need to do the operation at all if ( ( fSupportedFeatures & kIOATAFeatureAdvancedPowerManagement ) == 0 ) return status; status = ataCommandSetFeatures ( kATAEnableAPM, fAPMLevel, 0, 0, 0, mATAFlagImmediate, forceSync ); STATUS_LOG ( ( "IOATABlockStorageDriver::setAdvancedPowerManagementLevel exiting with error = %ld.\n", ( UInt32 ) status ) ); return status; } //-------------------------------------------------------------------------------------- // ¥ sATAVoidCallback - callback that does nothing //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sATAVoidCallback ( IOATACommand * cmd ) { return; } //-------------------------------------------------------------------------------------- // ¥ sATAConfigStateMachine - state machine for configuration commands //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sATAConfigStateMachine ( IOATACommand * cmd ) { ATAConfigData * configData; IOATABlockStorageDriver * driver; STATUS_LOG ( ( "IOATABlockStorageDriver::sATAConfigStateMachine entering\n" ) ); configData = ( ATAConfigData * ) cmd->refCon; driver = configData->self; switch ( configData->state ) { case kPIOTransferModeSetup: configData->state = kPIOTransferModeDone; driver->setPIOTransferMode ( false ); break; case kPIOTransferModeDone: if ( ( driver->fUltraDMAMode != 0 ) || ( driver->fDMAMode != 0 ) ) { configData->state = kDMATransferModeDone; driver->setDMATransferMode ( false ); break; } // Intentional fall through in case there is no DMA mode available case kDMATransferModeDone: { configData->state = kReadAheadEnableDone; driver->ataCommandSetFeatures ( kATAEnableReadAhead, 0, 0, 0, 0, mATAFlagImmediate, false ); } break; case kReadAheadEnableDone: { configData->state = kWriteCacheEnableDone; driver->ataCommandSetFeatures ( kATAEnableWriteCache, 0, 0, 0, 0, mATAFlagImmediate, false ); } break; case kWriteCacheEnableDone: // we're done with configuration configData->syncer->signal ( kIOReturnSuccess, false ); break; default: PANIC_NOW ( ( "sATAPIConfigStateMachine unexpected state\n" ) ); break; } } //-------------------------------------------------------------------------------------- // ¥ resetATADevice - Sends a device reset command to the device //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::resetATADevice ( void ) { IOReturn status = kIOReturnSuccess; STATUS_LOG ( ( "IOATABlockStorageDriver::resetATADevice entering.\n" ) ); SERIAL_STATUS_LOG ( ( "IOATABlockStorageDriver::resetATADevice executing reset.\n" ) ); if ( fResetInProgress ) return kIOReturnNotPermitted; fResetInProgress = true; // Zero the command object fResetCommand->zeroCommand ( ); // Start filling in the command fResetCommand->setUnit ( fATAUnitID ); fResetCommand->setTimeoutMS ( kATATimeout45Seconds ); fResetCommand->setOpcode ( kATAFnBusReset ); fResetCommand->setFlags ( mATAFlagImmediate ); fResetCommand->setCallbackPtr ( &IOATABlockStorageDriver::sHandleReset ); fResetCommand->refCon = ( void * ) this; status = fATADevice->executeCommand ( fResetCommand ); if ( status != kATANoErr ) { // There was a problem selecting the device for reset. This is // where we panic the system and let them know that the ATA // device has wedged. Eventually, we will make this better so // that non-backing store devices will be able to tear down the // driver stack instead of panic'ing the system. panic ( "ATA Disk: Error when attempting to reset device, status = 0x%08x\n", status ); } fResetInProgress = false; return status; } #pragma mark - #pragma mark Static Routines //-------------------------------------------------------------------------------------- // ¥ sHandleCommandCompletion - This routine is called by our provider when a // command processing has completed //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sHandleCommandCompletion ( IOATACommand * cmd ) { ATAClientData clientData = { 0 }; UInt32 result = kATANoErr; STATUS_LOG ( ( "IOATABlockStorageDriver::sHandleCommandCompletion entering.\n" ) ); assert ( cmd != NULL ); bcopy ( cmd->refCon, &clientData, sizeof ( clientData ) ); result = cmd->getResult ( ); // Return IOReturn for sync commands. switch ( result ) { case kATANoErr: STATUS_LOG ( ( "IOATABlockStorageDriver::sHandleCommandCompletion actual xfer = %ld.\n", ( UInt32 ) cmd->getActualTransfer ( ) ) ); clientData.returnCode = kIOReturnSuccess; break; case kATATimeoutErr: case kATAErrDevBusy: // Reset the device because the device is hung clientData.self->resetATADevice ( ); // Intentional fall through case kATADeviceError: ERROR_LOG ( ( "IOATABlockStorageDriver::sHandleCommandCompletion result = %ld.\n", result ) ); // Reissue the command if the max number of retries is greater // than zero, else return an error if ( clientData.maxRetries > 0 ) { sReissueCommandFromClientData ( cmd ); return; } break; default: break; } clientData.self->fNumCommandsOutstanding--; if ( clientData.isSync ) { clientData.returnCode = result; // For sync commands, unblock the client thread. assert ( clientData.completion.syncLock ); clientData.completion.syncLock->signal ( ); } else { UInt64 bytesTransferred = 0; if ( clientData.self == NULL ) { panic ( "ATA controller called me and my completion data is NULL" ); } bytesTransferred = ( UInt64 ) cmd->getActualTransfer ( ); // Async command processing is complete, re-enqueue command clientData.self->returnATACommandObject ( cmd ); // Signal the completion routine that the request has been completed. IOStorage::complete ( clientData.completion.async, result, bytesTransferred ); } STATUS_LOG ( ( "IOATABlockStorageDriver::sHandleCommandCompletion exiting.\n" ) ); } //--------------------------------------------------------------------------- // ¥ sHandleSimpleSyncTransaction - callback routine for simple synchronous // commands //--------------------------------------------------------------------------- void IOATABlockStorageDriver::sHandleSimpleSyncTransaction ( IOATACommand * cmd ) { IOReturn status; IOSyncer * syncer; syncer = ( IOSyncer * ) cmd->refCon; status = cmd->getResult ( ); syncer->signal ( status, false ); } //--------------------------------------------------------------------------- // ¥ sHandleReset - callback routine for reset commands //--------------------------------------------------------------------------- void IOATABlockStorageDriver::sHandleReset ( IOATACommand * cmd ) { IOATABlockStorageDriver * self; if ( cmd->getResult ( ) == kATANoErr ) { self = ( IOATABlockStorageDriver * ) cmd->refCon; self->fWakeUpResetOccurred = true; } } //-------------------------------------------------------------------------------------- // ¥ sConvertBlockToCHSAddress - Converts a block to a valid CHS address and stuffs // it into the command //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sConvertBlockToCHSAddress ( IOATACommand * cmd, UInt32 block, UInt32 heads, UInt32 sectors, ataUnitID unitID ) { ATAClientData * clientData; clientData = ( ATAClientData * ) cmd->refCon; UInt16 startingCylinder = block / ( sectors * heads ); UInt16 whichHead = ( block / sectors ) % heads; cmd->setSectorNumber ( ( block % sectors ) + 1 ); // Sectors range from 1Én cmd->setCylLo ( ( UInt8 ) startingCylinder ); cmd->setCylHi ( ( UInt8 ) ( startingCylinder >> 8 ) ); cmd->setDevice_Head ( ( ( ( UInt8 ) unitID ) << 4 ) | ( whichHead & mATAHeadNumber ) ); // Save extra state data clientData->sectorNumber = ( block % sectors ) + 1; clientData->cylLow = ( UInt8 ) startingCylinder; clientData->cylHigh = ( UInt8 ) ( startingCylinder >> 8 ); } //-------------------------------------------------------------------------------------- // ¥ sReissueCommandFromClientData - Reissue a transaction //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sReissueCommandFromClientData ( IOATACommand * cmd ) { ATAClientData * clientData; clientData = ( ATAClientData * ) cmd->refCon; if ( clientData == NULL ) { PANIC_NOW ( ( "No client data associated with this command.\n" ) ); return; } clientData->maxRetries--; // Zero the command cmd->zeroCommand ( ); // Rebuild the ATA command based on saved register state cmd->setOpcode ( clientData->opCode ); cmd->setFlags ( clientData->flags ); cmd->setUnit ( clientData->self->fATAUnitID ); cmd->setTimeoutMS ( clientData->timeout ); cmd->setCallbackPtr ( &IOATABlockStorageDriver::sHandleCommandCompletion ); cmd->setRegMask ( clientData->regMask ); cmd->setBuffer ( clientData->buffer ); cmd->setPosition ( clientData->bufferOffset ); cmd->setByteCount ( clientData->numberOfBlocks * kATADefaultSectorSize ); cmd->setTransferChunkSize ( kATADefaultSectorSize ); cmd->setFeatures ( clientData->featuresReg ); cmd->setSectorCount ( clientData->sectorCount ); cmd->setSectorNumber ( clientData->sectorNumber ); cmd->setCylLo ( clientData->cylLow ); cmd->setCylHi ( clientData->cylHigh ); cmd->setDevice_Head ( clientData->sdhReg ); cmd->setCommand ( clientData->command ); if ( clientData->useExtendedLBA ) { IOExtendedLBA * extLBA = cmd->getExtendedLBA ( ); extLBA->setLBALow16 ( clientData->lbaLow16 ); extLBA->setLBAMid16 ( clientData->lbaMid16 ); extLBA->setLBAHigh16 ( clientData->lbaHigh16 ); extLBA->setSectorCount16 ( clientData->sectorCount16 ); extLBA->setFeatures16 ( clientData->features16 ); extLBA->setDevice ( clientData->device ); extLBA->setCommand ( clientData->command16 ); } clientData->self->fATADevice->executeCommand ( cmd ); STATUS_LOG ( ( "%ssReissueCommandFromClientData%s\n", "\033[33m", "\033[0m" ) ); } //-------------------------------------------------------------------------------------- // ¥ sSetCommandBuffers - Builds an ATA command based on passed in arguments //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sSetCommandBuffers ( IOATACommand * cmd, IOMemoryDescriptor * buffer, IOByteCount offset, IOByteCount numBlocks ) { ATAClientData * clientData; clientData = ( ATAClientData * ) cmd->refCon; cmd->setBuffer ( buffer ); cmd->setPosition ( offset ); cmd->setByteCount ( numBlocks * kATADefaultSectorSize ); cmd->setTransferChunkSize ( kATADefaultSectorSize ); clientData->buffer = buffer; clientData->bufferOffset = offset; clientData->numberOfBlocks = numBlocks; } //-------------------------------------------------------------------------------------- // ¥ sSaveStateData - Saves state data for executing commands //-------------------------------------------------------------------------------------- void IOATABlockStorageDriver::sSaveStateData ( IOATACommand * cmd ) { ATAClientData * clientData; clientData = ( ATAClientData * ) cmd->refCon; clientData->sectorCount = cmd->getSectorCount ( ); clientData->sectorNumber = cmd->getSectorNumber ( ); clientData->cylHigh = cmd->getCylHi ( ); clientData->cylLow = cmd->getCylLo ( ); clientData->sdhReg = cmd->getDevice_Head ( ); } //-------------------------------------------------------------------------------------- // ¥ sValidateIdentifyData - Validates identify data by checksumming it //-------------------------------------------------------------------------------------- IOReturn IOATABlockStorageDriver::sValidateIdentifyData ( UInt8 * deviceIdentifyData ) { IOReturn status = kIOReturnSuccess; UInt8 checkSum = 0; UInt32 index = 0; UInt16 offset = kATAIdentifyIntegrity * sizeof ( UInt16 ); if ( deviceIdentifyData[offset] == kChecksumValidCookie ) { for ( index = 0; index < 512; index++ ) checkSum += deviceIdentifyData[index]; if ( checkSum != 0 ) { IOLog ( "ATA Disk: Identify data is incorrect - bad checksum\n" ); } } return status; }