/* * Copyright (c) 1998-2001 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 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 // SCSI Architecture definitions header files #include #include // Definition for this class #include #include "SCSITaskLib.h" #include "SCSITaskLibPriv.h" // For debugging, set SCSI_PERIPHERAL_DEVICE_NUB_DEBUGGING_LEVEL to one // of the following values: // 0 No debugging (GM release level) // 1 PANIC_NOW only // 2 PANIC_NOW and ERROR_LOG // 3 PANIC_NOW, ERROR_LOG and STATUS_LOG #if ( SCSI_PERIPHERAL_DEVICE_NUB_DEBUGGING_LEVEL >= 1 ) #define PANIC_NOW(x) IOPanic x #else #define PANIC_NOW(x) #endif #if ( SCSI_PERIPHERAL_DEVICE_NUB_DEBUGGING_LEVEL >= 2 ) #define ERROR_LOG(x) IOLog x #else #define ERROR_LOG(x) #endif #if ( SCSI_PERIPHERAL_DEVICE_NUB_DEBUGGING_LEVEL >= 3 ) #define STATUS_LOG(x) IOLog x #else #define STATUS_LOG(x) #endif #define kMaxInquiryAttempts 8 #define super IOSCSIProtocolServices OSDefineMetaClassAndStructors ( IOSCSIPeripheralDeviceNub, IOSCSIProtocolServices ); bool IOSCSIPeripheralDeviceNub::init ( OSDictionary * propTable ) { // Pass to super class for inspection if ( super::init ( propTable ) == false ) { // Super didn't like it, return false return false; } return true; } bool IOSCSIPeripheralDeviceNub::start ( IOService * provider ) { OSDictionary * characterDict = NULL; if ( !super::start ( provider ) ) { return false; } fProvider = OSDynamicCast ( IOSCSIPeripheralDeviceNub, provider ); if ( fProvider != NULL ) { // Our provider is one of us, don't load again. return false; } fProvider = OSDynamicCast ( IOSCSIProtocolServices, provider ); if ( fProvider == NULL ) { ERROR_LOG ( ( "%s: Provider was not a IOSCSIProtocolServices object\n", getName ( ) ) ); return false; } fSCSIPrimaryCommandObject = new SCSIPrimaryCommands; if ( fSCSIPrimaryCommandObject == NULL ) { ERROR_LOG ( ( "%s: Could not allocate a SCSIPrimaryCommands object\n", getName ( ) ) ); return false; } if ( fProvider->open ( this ) == false ) { ERROR_LOG ( ( "%s: Could not open provider\n", getName ( ) ) ); if ( fSCSIPrimaryCommandObject != NULL ) { fSCSIPrimaryCommandObject->release ( ); fSCSIPrimaryCommandObject = NULL; } return false; } STATUS_LOG ( ( "%s: Check for the SCSI Device Characteristics property from provider.\n", getName ( ) ) ); // Check if the personality for this device specifies a preferred protocol if ( ( fProvider->getProperty ( kIOPropertySCSIDeviceCharacteristicsKey ) ) == NULL ) { STATUS_LOG ( ( "%s: No SCSI Device Characteristics property, set defaults.\n", getName ( ) ) ); fDefaultInquiryCount = 0; } else { STATUS_LOG ( ( "%s: Get the SCSI Device Characteristics property from provider.\n", getName ( ) ) ); characterDict = OSDynamicCast ( OSDictionary, ( fProvider->getProperty ( kIOPropertySCSIDeviceCharacteristicsKey ) ) ); STATUS_LOG ( ( "%s: set this SCSI Device Characteristics property.\n", getName ( ) ) ); setProperty ( kIOPropertySCSIDeviceCharacteristicsKey, characterDict ); // Check if the personality for this device specifies a preferred protocol STATUS_LOG ( ( "%s: check for the Inquiry Length property.\n", getName ( ) ) ); if ( characterDict->getObject ( kIOPropertySCSIInquiryLengthKey ) == NULL ) { STATUS_LOG ( ( "%s: No Inquiry Length property, use default.\n", getName ( ) ) ); fDefaultInquiryCount = 0; } else { OSNumber * defaultInquiry; STATUS_LOG ( ( "%s: Get Inquiry Length property.\n", getName ( ) ) ); defaultInquiry = OSDynamicCast ( OSNumber, characterDict->getObject ( kIOPropertySCSIInquiryLengthKey ) ); // This device has a preferred protocol, use that. fDefaultInquiryCount = defaultInquiry->unsigned32BitValue ( ); } } STATUS_LOG ( ( "%s: default inquiry count is: %d\n", getName ( ), fDefaultInquiryCount ) ); if ( InterrogateDevice ( ) == true ) { OSObject * obj; STATUS_LOG ( ( "%s: The device has been interrogated, register services.\n", getName ( ) ) ); setProperty ( kIOMatchCategoryKey, kSCSITaskUserClientIniterKey ); characterDict = OSDynamicCast ( OSDictionary, fProvider->getProperty ( kIOPropertyProtocolCharacteristicsKey ) ); if ( characterDict == NULL ) { characterDict = OSDictionary::withCapacity ( 1 ); } else { characterDict->retain ( ); } obj = fProvider->getProperty ( kIOPropertyPhysicalInterconnectTypeKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyPhysicalInterconnectTypeKey, obj ); } obj = fProvider->getProperty ( kIOPropertyPhysicalInterconnectLocationKey ); if ( obj != NULL ) { characterDict->setObject ( kIOPropertyPhysicalInterconnectLocationKey, obj ); } setProperty ( kIOPropertyProtocolCharacteristicsKey, characterDict ); characterDict->release ( ); // Register this object as a nub for the Logical Unit Driver. registerService ( ); STATUS_LOG ( ( "%s: Registered and setup is complete\n", getName ( ) ) ); // Setup was successful, return true. return true; } // The setup was unsuccessful, return false to allow this object to // get removed. stop ( provider ); return false; } void IOSCSIPeripheralDeviceNub::stop ( IOService * provider ) { if ( fProvider && ( fProvider == provider ) ) { STATUS_LOG ( ("%s: stop called\n", getName( ) ) ); if ( fSCSIPrimaryCommandObject != NULL ) { fSCSIPrimaryCommandObject->release ( ); fSCSIPrimaryCommandObject = NULL; } super::stop ( provider ); } } IOReturn IOSCSIPeripheralDeviceNub::message ( UInt32 type, IOService * nub, void * arg ) { IOReturn status = kIOReturnSuccess; switch ( type ) { // The device is no longer attached to the bus // close provider so message will continue // to propagate up the driver stack. case kIOMessageServiceIsRequestingClose: { STATUS_LOG ( ("%s: kIOMessageServiceIsRequestingClose called\n", getName( ) ) ); if ( fProvider ) { STATUS_LOG ( ("%s: closing provider\n", getName( ) ) ); fProvider->close ( this ); } } break; case kSCSIProtocolNotification_VerifyDeviceState: { messageClients ( kSCSIProtocolNotification_VerifyDeviceState, NULL, NULL ); status = kIOReturnSuccess; } break; default: { STATUS_LOG ( ("%s: some message = %ld called\n", getName ( ), type ) ); status = super::message ( type, nub, arg ); } break; } return status; } #pragma mark - #pragma mark Power Management Utility Methods void IOSCSIPeripheralDeviceNub::joinPMtree ( IOService * driver ) { STATUS_LOG ( ( "%s%s::%s called%s\n", "\033[33m", getName ( ), __FUNCTION__, "\033[0m" ) ); fProvider->joinPMtree ( driver ); STATUS_LOG ( ( "%s%s::%s finished%s\n", "\033[33m", getName ( ), __FUNCTION__, "\033[0m" ) ); } void IOSCSIPeripheralDeviceNub::InitializePowerManagement ( IOService * provider ) { // This piece does not get any power management // since it is just a relay class. Do NOT call registerPowerDriver // and do not call super::InitializePowerManagement // which does that. STATUS_LOG ( ( "%s%s::%s not doing anything%s\n", "\033[33m", getName ( ), __FUNCTION__, "\033[0m" ) ); } #pragma mark - #pragma mark Property Table Utility Methods bool IOSCSIPeripheralDeviceNub::matchPropertyTable ( OSDictionary * table, SInt32 * score ) { bool returnValue = true; bool isMatch = false; SInt32 propertyScore = *score; // Clamp the copy of the score to 4000 to prevent us from promoting a device // driver a full ranking order. Our increments for ranking order increase by 5000 // and we add propertyScore to *score on exit if the exiting value of *score is non-zero. if ( propertyScore >= 5000 ) { propertyScore = 4000; } if ( sCompareIOProperty ( this, table, kIOPropertySCSIPeripheralDeviceType, &isMatch ) ) { if ( isMatch ) { *score = kDefaultProbeRanking; } else { *score = kPeripheralDeviceTypeNoMatch; returnValue = false; } if ( sCompareIOProperty ( this, table, kIOPropertySCSIVendorIdentification, &isMatch ) ) { if ( isMatch ) { *score = kFirstOrderRanking; if ( sCompareIOProperty ( this, table, kIOPropertySCSIProductIdentification, &isMatch ) ) { if ( isMatch ) { *score = kSecondOrderRanking; if ( sCompareIOProperty ( this, table, kIOPropertySCSIProductRevisionLevel, &isMatch ) ) { if ( isMatch ) { *score = kThirdOrderRanking; } else { *score = kPeripheralDeviceTypeNoMatch; returnValue = false; } } } else { *score = kPeripheralDeviceTypeNoMatch; returnValue = false; } } } else { *score = kPeripheralDeviceTypeNoMatch; returnValue = false; } } } else { // We need to special case the SCSITaskUserClient "driver" // because if no driver matches, then we want it to match on the device OSString * string; string = OSDynamicCast ( OSString, table->getObject ( kIOMatchCategoryKey ) ); if ( string != NULL ) { if ( strcmp ( string->getCStringNoCopy ( ), kIOPropertySCSITaskUserClientDevice ) ) { *score = kDefaultProbeRanking - 100; } } else { *score = kPeripheralDeviceTypeNoMatch; returnValue = false; } } if ( *score != 0 ) *score += propertyScore; return returnValue; } bool IOSCSIPeripheralDeviceNub::sCompareIOProperty ( IOService * object, OSDictionary * table, char * propertyKeyName, bool * matches ) { OSObject * tableObject; OSObject * deviceObject; bool returnValue = false; *matches = false; tableObject = table->getObject ( propertyKeyName ); deviceObject = object->getProperty ( propertyKeyName ); if ( ( deviceObject != NULL ) && ( tableObject != NULL ) ) { returnValue = true; *matches = deviceObject->isEqualTo( tableObject ); } return returnValue; } #pragma mark - #pragma mark Provided Services Methods bool IOSCSIPeripheralDeviceNub::SendSCSICommand ( SCSITaskIdentifier request, SCSIServiceResponse * serviceResponse, SCSITaskStatus * taskStatus ) { return false; } SCSIServiceResponse IOSCSIPeripheralDeviceNub::AbortSCSICommand ( SCSITaskIdentifier request ) { return kSCSIServiceResponse_FUNCTION_REJECTED; } // The ExecuteCommand function will take a SCSITask Object and transport // it across the physical wire(s) to the device void IOSCSIPeripheralDeviceNub::ExecuteCommand ( SCSITaskIdentifier request ) { fProvider->ExecuteCommand ( request ); } // The AbortCommand function will abort the indicated SCSITask object, // if it is possible and the SCSITask has not already completed. SCSIServiceResponse IOSCSIPeripheralDeviceNub::AbortCommand ( SCSITaskIdentifier abortTask ) { return fProvider->AbortCommand ( abortTask ); } // The IsProtocolServiceSupported will return true if the specified // feature is supported by the protocol layer. If the service has a value that must be // returned, it will be returned in the serviceValue output parameter. bool IOSCSIPeripheralDeviceNub::IsProtocolServiceSupported ( SCSIProtocolFeature feature, void * serviceValue ) { return fProvider->IsProtocolServiceSupported ( feature, serviceValue ); } bool IOSCSIPeripheralDeviceNub::HandleProtocolServiceFeature ( SCSIProtocolFeature feature, void * serviceValue ) { return fProvider->HandleProtocolServiceFeature ( feature, serviceValue ); } #pragma mark - #pragma mark Private method declarations void IOSCSIPeripheralDeviceNub::TaskCallback ( SCSITaskIdentifier completedTask ) { IOSyncer * fSyncLock; SCSIServiceResponse serviceResponse; SCSITask * scsiRequest; STATUS_LOG ( ( "IOSCSIPeripheralDeviceNub::TaskCallback called\n.") ); scsiRequest = OSDynamicCast ( SCSITask, completedTask ); if ( scsiRequest == NULL ) { PANIC_NOW ( ( "IOSCSIPeripheralDeviceNub::TaskCallback scsiRequest==NULL." ) ); ERROR_LOG ( ( "IOSCSIPeripheralDeviceNub::TaskCallback scsiRequest==NULL." ) ); return; } fSyncLock = ( IOSyncer * ) scsiRequest->GetApplicationLayerReference ( ); serviceResponse = scsiRequest->GetServiceResponse ( ); fSyncLock->signal ( serviceResponse, false ); } SCSIServiceResponse IOSCSIPeripheralDeviceNub::SendTask ( SCSITask * request ) { SCSIServiceResponse serviceResponse; IOSyncer * fSyncLock; fSyncLock = IOSyncer::create ( false ); if ( fSyncLock == NULL ) { PANIC_NOW ( ( "IOSCSIPeripheralDeviceNub::SendTask Allocate fSyncLock failed." ) ); ERROR_LOG ( ( "IOSCSIPeripheralDeviceNub::TaskCallback scsiRequest==NULL." ) ); return kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; } fSyncLock->signal ( kIOReturnSuccess, false ); request->SetTaskCompletionCallback ( &this->TaskCallback ); request->SetApplicationLayerReference ( ( void * ) fSyncLock ); fSyncLock->reinit ( ); STATUS_LOG ( ( "%s:SendTask Execute the command.\n", getName ( ) ) ); fProvider->ExecuteCommand ( request ); STATUS_LOG ( ( "%s:SendTask wait for the signal.\n", getName ( ) ) ); // Wait for the completion routine to get called serviceResponse = ( SCSIServiceResponse ) fSyncLock->wait ( false ); fSyncLock->release ( ); STATUS_LOG ( ( "%s:SendTask return the service response.\n", getName ( ) ) ); return serviceResponse; } bool IOSCSIPeripheralDeviceNub::InterrogateDevice ( void ) { OSString * string; SCSICmd_INQUIRY_StandardData * inqData = NULL; UInt8 inqDataCount; int loopCount; char tempString[17]; // Maximum + 1 for null char IOMemoryDescriptor * bufferDesc = NULL; SCSITask * request = NULL; // Before we register ourself as a nub, we need to find out what // type of device we want to connect to us // Do an Inquiry command and parse the data to determine the peripheral // device type. if ( fDefaultInquiryCount == 0 ) { // There is no default Inquiry count for this device, use the standard // structure size. STATUS_LOG ( ( "%s: use sizeof(SCSICmd_INQUIRY_StandardData) for Inquiry.\n", getName ( ) ) ); inqDataCount = sizeof ( SCSICmd_INQUIRY_StandardData ); } else { // This device has a default inquiry count, use it. STATUS_LOG ( ( "%s: use fDefaultInquiryCount for Inquiry.\n", getName ( ) ) ); inqDataCount = fDefaultInquiryCount; } inqData = ( SCSICmd_INQUIRY_StandardData * ) IOMalloc ( inqDataCount ); if ( inqData == NULL ) { STATUS_LOG ( ( "%s: Couldn't allocate Inquiry buffer.\n", getName ( ) ) ); return false; } bufferDesc = IOMemoryDescriptor::withAddress ( ( void * ) inqData, inqDataCount, kIODirectionIn ); if ( bufferDesc == NULL ) { STATUS_LOG ( ( "%s: Couldn't allocate Inquiry buffer descriptor.\n", getName ( ) ) ); IOFree ( ( void * ) inqData, inqDataCount ); inqData = NULL; return false; } bool cmdValid; SCSIServiceResponse serviceResponse; SCSI_Sense_Data senseBuffer; STATUS_LOG ( ( "%s: Send a Test Unit Ready to prime the device.\n", getName ( ) ) ); request = new SCSITask; cmdValid = fSCSIPrimaryCommandObject->TEST_UNIT_READY ( request, 0x00 ); if ( cmdValid == true ) { // The command was successfully built, now send it serviceResponse = SendTask ( request ); if ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE ) { bool validSense = false; STATUS_LOG ( ( "%s: Test Unit Ready completed.\n", getName ( ) ) ); if ( request->GetTaskStatus ( ) == kSCSITaskStatus_CHECK_CONDITION ) { ERROR_LOG ( ( "%s: Check condition occurred.\n", getName ( ) ) ); validSense = request->GetAutoSenseData ( &senseBuffer ); if ( validSense == false ) { request->ResetForNewTask ( ); cmdValid = fSCSIPrimaryCommandObject->REQUEST_SENSE ( request, bufferDesc, kSenseDefaultSize, 0 ); if ( cmdValid == true ) { ERROR_LOG ( ( "%s: No autosense. Send request Sense.\n", getName ( ) ) ); serviceResponse = SendTask ( request ); } } } } } IOSleep ( 100 ); if ( isInactive ( ) ) { goto ERROR_EXIT; } // We have seen cases in which a device will never succeed because the // device is wedged or gone - put upper limit to retries. for ( loopCount = 0; loopCount < kMaxInquiryAttempts; loopCount++ ) { cmdValid = fSCSIPrimaryCommandObject->INQUIRY ( request, bufferDesc, 0, 0, 0, inqDataCount, 0 ); if ( cmdValid == true ) { // The command was successfully built, now send it STATUS_LOG ( ( "%s: Send the Inquiry command.\n", getName ( ) ) ); serviceResponse = SendTask ( request ); } else { ERROR_LOG ( ( "%s: INQUIRY command failed\n", getName ( ) ) ); request->release ( ); goto ERROR_EXIT; } if ( serviceResponse != kSCSIServiceResponse_TASK_COMPLETE ) { IOSleep ( 1000 ); } else { break; } // If we are terminated while spinning in this loop - bail if ( isInactive ( ) ) { break; } } // We are finished with the buffer, release the object. bufferDesc->release ( ); bufferDesc = NULL; request->release ( ); if ( isInactive ( ) ) { goto ERROR_EXIT; } // Set the Peripheral Device Type property for the device. setProperty ( kIOPropertySCSIPeripheralDeviceType, ( UInt64 ) ( inqData->PERIPHERAL_DEVICE_TYPE & kINQUIRY_PERIPHERAL_TYPE_Mask ), 8 ); // Set the Vendor Identification property for the device. for ( loopCount = 0; loopCount < kINQUIRY_VENDOR_IDENTIFICATION_Length; loopCount++ ) { tempString[loopCount] = inqData->VENDOR_IDENTIFICATION[loopCount]; } tempString[loopCount] = 0; for ( loopCount = kINQUIRY_VENDOR_IDENTIFICATION_Length - 1; loopCount >= 0; loopCount-- ) { if ( tempString[loopCount] != ' ' ) { // Found a real character tempString[loopCount+1] = '\0'; break; } } string = OSString::withCString ( tempString ); if ( string != NULL ) { setProperty ( kIOPropertySCSIVendorIdentification, string ); string->release ( ); } // Set the Product Indentification property for the device. for ( loopCount = 0; loopCount < kINQUIRY_PRODUCT_IDENTIFICATION_Length; loopCount++ ) { tempString[loopCount] = inqData->PRODUCT_INDENTIFICATION[loopCount]; } tempString[loopCount] = 0; for ( loopCount = kINQUIRY_PRODUCT_IDENTIFICATION_Length - 1; loopCount >= 0; loopCount-- ) { if ( tempString[loopCount] != ' ' ) { // Found a real character tempString[loopCount+1] = '\0'; break; } } string = OSString::withCString ( tempString ); if ( string != NULL ) { setProperty ( kIOPropertySCSIProductIdentification, string ); string->release ( ); } // Set the Product Revision Level property for the device. for ( loopCount = 0; loopCount < kINQUIRY_PRODUCT_REVISION_LEVEL_Length; loopCount++ ) { tempString[loopCount] = inqData->PRODUCT_REVISION_LEVEL[loopCount]; } tempString[loopCount] = 0; for ( loopCount = kINQUIRY_PRODUCT_REVISION_LEVEL_Length - 1; loopCount >= 0; loopCount-- ) { if ( tempString[loopCount] != ' ' ) { // Found a real character tempString[loopCount+1] = '\0'; break; } } string = OSString::withCString ( tempString ); if ( string != NULL ) { setProperty ( kIOPropertySCSIProductRevisionLevel, string ); string->release ( ); } // This object is done with the Inquiry data, free it now if ( inqData != NULL ) { IOFree ( ( void * ) inqData, inqDataCount ); inqData = NULL; } return true; ERROR_EXIT: ERROR_LOG ( ( "%s: aborting startup.\n", getName ( ) ) ); if ( bufferDesc != NULL ) { bufferDesc->release ( ); bufferDesc = NULL; } if ( inqData != NULL ) { IOFree ( ( void * ) inqData, inqDataCount ); inqData = NULL; } return false; } // Space reserved for future expansion. OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 1 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 2 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 3 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 4 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 5 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 6 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 7 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 8 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 9 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 10 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 11 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 12 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 13 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 14 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 15 ); OSMetaClassDefineReservedUnused( IOSCSIPeripheralDeviceNub, 16 );