/*
 * Copyright (c) 2000 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@
 */

/*
 *
 *	ATADeviceNub.cpp
 *
 */

 
#include <IOKit/IOTypes.h>
#include "IOATATypes.h"
#include "IOATADevice.h"
#include "IOATAController.h"
#include "ATADeviceNub.h"
#include "IOATADevConfig.h"

#include <IOKit/IOSyncer.h>

enum{

	kDoIDDataComplete,
	kDoSetFeatureComplete
};


struct completionInfo{

	UInt32 whatToDo;
	IOSyncer* sync;	

};

#ifdef DLOG
#undef DLOG
#endif

#ifdef  ATA_DEBUG
#define DLOG(fmt, args...)  IOLog(fmt, ## args)
#else
#define DLOG(fmt, args...)
#endif


#define kIDBufferBytes 512

//---------------------------------------------------------------------------

#define super IOATADevice

OSDefineMetaClassAndStructors(   ATADeviceNub, IOATADevice )
    OSMetaClassDefineReservedUnused(ATADeviceNub, 0);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 1);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 2);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 3);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 4);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 5);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 6);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 7);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 8);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 9);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 10);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 11);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 12);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 13);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 14);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 15);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 16);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 17);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 18);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 19);
    OSMetaClassDefineReservedUnused(ATADeviceNub, 20);

//---------------------------------------------------------------------------



// static creator function - used by IOATAControllers to create nubs.
ATADeviceNub* 
ATADeviceNub::ataDeviceNub( IOATAController* provider, ataUnitID unit, ataDeviceType devType)
{

	ATADeviceNub*  nub = new ATADeviceNub;

	if( ! nub )
		return 0L;
	
	if( !nub->init( provider, unit, devType) )
		return 0L;
		
	return nub;

}


//---------------------------------------------------------------------------

bool 
ATADeviceNub::init(IOATAController* provider, ataUnitID unit, ataDeviceType devType)
{

	if( !super::init( (OSDictionary*) 0L) )
		return false;

	_provider = provider;
	_unitNumber = unit;
	_deviceType = devType;

	// allocate a buffer for the identify info from the device	
	buffer = (UInt8*) IOMalloc( kIDBufferBytes );
	
	if( !buffer )
		return false;
	
	IOReturn err = kATANoErr;
	
	// issue the identify command so we can get the vendor strings
	err = getDeviceID();

	if( err )
	{
		DLOG("ATADeviceNub failed identify device %ld\n", (long int) err);

		IOFree( buffer, kIDBufferBytes);	
		return false;	
	}
	
	publishProperties();
	publishBusProperties();
	publishVendorProperties();

	IOFree( buffer, kIDBufferBytes);	
	buffer = 0L;
		
	return true;

}



//---------------------------------------------------------------------------
 

//---------------------------------------------------------------------------
bool
ATADeviceNub::attach(IOService* provider )
{

	IOATAController* controller = OSDynamicCast( IOATAController, provider);
	
	if( !controller )
	{
		DLOG("ATANub: Provider not IOATAController\n");
		return false;
	}


	if( !super::attach( provider) )
		return false;

		
	return true;

}



//---------------------------------------------------------------------------

// create and destroy IOATACommands
//---------------------------------------------------------------------------

IOATACommand*	
ATADeviceNub::allocCommand( void )
{

	IOATABusCommand64* cmd = IOATABusCommand64::allocateCmd32();
	
	return (IOATACommand*) cmd;

}
	
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
void
ATADeviceNub::freeCommand( IOATACommand* inCommand)
{
	
	inCommand->release();
}




//---------------------------------------------------------------------------


//---------------------------------------------------------------------------

// Submit IO requests 
IOReturn		
ATADeviceNub::executeCommand(IOATACommand* command)
{

	IOSyncer* mySync = 0L;
	IOATABusCommand* cmd = OSDynamicCast( IOATABusCommand, command);
	
	if( !cmd )
		return -1;
		
	if( cmd->getCallbackPtr() == 0L)
	{
		mySync = IOSyncer::create();
		cmd->syncer = mySync;
	
	}

	IOReturn err = _provider->executeCommand( this, cmd);	

	if( mySync )
	{
		mySync->wait();
		err = cmd->getResult();
	}
	
	return err;	

}



/*---------------------------------------------------------------------------




---------------------------------------------------------------------------*/
IOReturn 
ATADeviceNub::getDeviceID( void )
{
	OSString* string;

	IOMemoryDescriptor* desc = IOMemoryDescriptor::withAddress((void *) buffer,
                                            kIDBufferBytes,
                                            kIODirectionIn);

	if( !desc )
	{
		
 		string = OSString::withCString( "failed" );
		setProperty( "Alloc descriptor", (OSObject *)string );
	 	string->release();
	 	return -1;	
	
	}

	IOATABusCommand* cmd = (IOATABusCommand*) allocCommand();
	
	if(!cmd)
	{
		
 		string = OSString::withCString( "failed" );
		setProperty( "Alloc command", (OSObject *)string );
	 	string->release();
		return -1;
	}	
	
	
	// tell the bus what to do, what unit and how long to allow
	cmd->setOpcode( kATAFnExecIO);
	cmd->setFlags(mATAFlagIORead);
	cmd->setUnit( _unitNumber  );
	cmd->setTimeoutMS( 30000);

	// setup the buffer for the data
	cmd->setBuffer ( desc);
	cmd->setPosition ((IOByteCount) 0);
	cmd->setByteCount ((IOByteCount) kIDBufferBytes);


	// setup the actual taskfile params for the device
	// only two parameters are needed, the device bit for the unit
	// and the actual command for the device to execute
	cmd->setDevice_Head( ((UInt8)_unitNumber) << 4);

	if(_deviceType == kATADeviceType)
	{
		cmd->setCommand ( kATAcmdDriveIdentify );
	
	} else {
	
		cmd->setCommand ( 0xA1 );  // packet identify	
	}
	
	// set up a call back pointer for the command to complete. 
	// the IOATAController only allows async commands
	
	cmd->setCallbackPtr ( (IOATACompletionFunction*) MyATACallback);

	
	// set the refCon so the callback knows what to do.
	completionInfo* completion = (completionInfo*)IOMalloc(sizeof(completionInfo));
	completion->whatToDo = 	kDoIDDataComplete;
	completion->sync = IOSyncer::create();
	cmd->refCon = (void*) completion;
	cmd->refCon2 = (void*) this;
	
	desc->prepare(kIODirectionIn);
	// tell the bus to exec the command
	DLOG("Sending ID command to bus controller\n");	
	IOReturn err =	executeCommand( cmd);	
	DLOG("Command returned error = %ld\n",(long int)err );

	completion->sync->wait();
	
	desc->complete( kIODirectionIn );
	
	IOFree( completion, sizeof(completionInfo));

	if( cmd->getResult() )
	{
		err = cmd->getResult();
	}
	
	freeCommand(cmd);
	
#if defined(__BIG_ENDIAN__)
// The identify device info needs to be byte-swapped on ppc (big-endian) 
// 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.	
		swapBytes16( buffer, kIDBufferBytes);
#else /* __LITTLE_ENDIAN__ */
    // Swap the strings in the identify data.
    swapBytes16( &buffer[46], 8);   // Firmware revision
    swapBytes16( &buffer[54], 40);  // Model number
    swapBytes16( &buffer[20], 20);  // Serial number
#endif

	return err;
	
	// the 512 byte buffer should contain the correctly byte-ordered
	// raw identity info from the device at this point. 
}



//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

void 
ATADeviceNub::publishBusProperties( void )
{

	OSString* string;
//	OSNumber* number;

 	// get some bus info
 	
 	IOATABusInfo* theInfo = IOATABusInfo::atabusinfo();
 	if( !theInfo )
	{
		DLOG("ATANub IOATABusInfo alloc fail\n");

 		return;
 	}
 	
 	if(_provider->provideBusInfo( theInfo ))
 	{
 		// blow it off on error
		DLOG("ATANub provide info failed\n");
 		theInfo->release();
 		return;	
 	}
 	
 	switch( theInfo->getSocketType() )
 	{
 		case kInternalATASocket:
	 		string = OSString::withCString( kATAInternalSocketString );
 		break;
	
		case kMediaBaySocket:
 			string = OSString::withCString( kATAMediaBaySocketString );
 		break;
	
		case kPCCardSocket:
 			string = OSString::withCString( kATAPCCardSocketString );	
		break;

		case kInternalSATA:
 			string = OSString::withCString( kATAInternalSATAString );	
		break;

		case kSATABay:
 			string = OSString::withCString( kATASATABayString );	
		break;
		
		case kInternalSATA2:
 			string = OSString::withCString( kATAInternalSATA2 );	
		break;
		
		
		case kSATA2Bay:
 			string = OSString::withCString( kATASATA2BayString );	
		break;

	
 		default:
 			string = OSString::withCString( kATAUnkownSocketString );
		break;
	
 	}
 	
  	setProperty( kATASocketKey, (OSObject *)string );
 	string->release();


	// these properties may be published in the future 
	// if conditions warrant
/*	
	number = OSNumber::withNumber( theInfo->getPIOModes(), 32 );
	setProperty( "piomode bitmap", (OSObject *) number);
 	number->release();

	number = OSNumber::withNumber( theInfo->getDMAModes(), 32 );
	setProperty( "dmamode bitmap", (OSObject *) number);
 	number->release();

	number = OSNumber::withNumber( theInfo->getUltraModes(), 32 );
	setProperty( "ultramode bitmap", (OSObject *) number);
 	number->release();

	number = OSNumber::withNumber( theInfo->getUnits(), 32 );
	setProperty( "units on bus", (OSObject *) number);
 	number->release();
*/


	// these properties may be published in the future for support of advanced ATA modes.
/*	setProperty( "DMA supported", theInfo->supportsDMA());
	setProperty( "48-bit LBA supported", theInfo->supportsExtendedLBA());
	setProperty( "command overlap supported", theInfo->supportsOverlapped());
	setProperty( "DMA-Queued supported", theInfo->supportsDMAQueued());
*/

 theInfo->release();

}


//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
void 
ATADeviceNub::publishProperties( void )
{

		
	OSString* string;
	
	switch( _deviceType )
	{
		case kATADeviceType:
			string = OSString::withCString( kATATypeATAString );
		break;
	
		case kATAPIDeviceType:
			string = OSString::withCString( kATATypeATAPIString );
		break;
	
		default:
			string = OSString::withCString( kATATypeUnknownString );
		break;	
	}

 	setProperty( kATADevPropertyKey, (OSObject *)string );
 	string->release();
 	
 	
	OSNumber* number = OSNumber::withNumber( _unitNumber, 32 );

	setProperty( kATAUnitNumberKey, (OSObject *) number);
	setProperty( "IOUnit", (OSObject *) number);
 	number->release();
	
	if( _unitNumber == 0 )
	{
		setLocation("0");
	
	} else {
	
		setLocation("1");
	}
	
 

}

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
void
ATADeviceNub::publishVendorProperties(void)
{
	
	if( IOATADevConfig::sDriveSupports48BitLBA( ( const UInt16*) buffer ) )
	{
		UInt32 upperLBA, lowerLBA;
		OSNumber* extendedCapacity = OSNumber::withNumber( IOATADevConfig::sDriveExtendedLBASize(   &upperLBA, &lowerLBA, ( const UInt16*) buffer ), 32 );
		setProperty( "extended LBA capacity", (OSObject *) extendedCapacity);
		extendedCapacity->release();
	
	}
	// terminate the strings with 0's
	// this changes the identify data, so we MUST do this part last.
	buffer[94] = 0;
	buffer[40] = 0;

	// Model number runs from byte 54 to 93 inclusive - byte 94 is set to 
	// zero to terminate that string
	OSString* modelNum = OSString::withCString((const char*) &buffer[54]);

	// now that we have made a deep copy of the model string, poke a 0 into byte 54 
	// in order to terminate the fw-vers string which runs from bytes 46 to 53 inclusive.
	buffer[54] = 0;
	
	OSString* firmVers = OSString::withCString((const char*) &buffer[46]);

	// serial number runs from byte 20 to byte 39 inclusive and byte 40 has been terminated with a null
	OSString* serial = OSString::withCString( (const char*) &buffer[20]);	
	
 	setProperty( kATAVendorPropertyKey, (OSObject *)modelNum );
 	setProperty( kATARevisionPropertyKey, (OSObject *)firmVers );
 	setProperty( kATASerialNumPropertyKey, (OSObject *)serial );


	serial->release();
	modelNum->release();
	firmVers->release();

}


//---------------------------------------------------------------------------

//---------------------------------------------------------------------------



void
ATADeviceNub::MyATACallback(IOATACommand* command )
{
	if( command->getResult() )
	{
	
		DLOG("Command result error = %ld\n",(long int)command->getResult() );
	
	}


	ATADeviceNub* self = (ATADeviceNub*) command->refCon2;
	
	self->processCallback( command );



}

//---------------------------------------------------------------------------

//---------------------------------------------------------------------------

void
ATADeviceNub::processCallback(IOATACommand* command )
{
	completionInfo* completer = (completionInfo*) command->refCon;

	switch( completer->whatToDo )
	{
		case  kDoIDDataComplete:
		
			completer->sync->signal();
		break;
		
		
		
		// do nothing on set features.
		case kDoSetFeatureComplete:
		
			completer->sync->signal();
		default:
		break;
		
	}// end switch	

}

//---------------------------------------------------------------------------
void 
ATADeviceNub::swapBytes16( UInt8* dataBuffer, IOByteCount length)
{

	IOByteCount	i;
	UInt8	c;
	unsigned char* 	firstBytePtr;
	
	for (i = 0; i < length; i+=2)
	{
		firstBytePtr = dataBuffer;				// save pointer
		c = *dataBuffer++;						// Save Byte0, point to Byte1
		*firstBytePtr = *dataBuffer;			// Byte0 = Byte1
		*dataBuffer++= c;						// Byte1 = Byte0
	}


}




