MacIOATA.cpp   [plain text]


/*
 * Copyright (c) 2000-2008 Apple 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@
 */
/*
 *
 *	MacIOATA.cpp
 *	
 *	class defining the portions of MacIO ATA cells which are shared
 *	in common between OHare, Heathrow and Key Largo ATA Cells.
 *	These controllers share a common register file layout, interrupt 
 *	source format and all use DBDMA engines. These are different from 
 *	other ATA controllers, such as most PCI-IDE and PC-Card ATA ports.
 * 	Each cell type has some distinctive features that must be implemented
 *	by a specific driver subclass. As much common code as possible is 
 *	presented in this superclass.
 *
 *
 */
#ifdef __ppc__

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

#include <IOKit/ppc/IODBDMA.h>
#include <IOKit/IOMemoryCursor.h>

#include <libkern/OSByteOrder.h>
#include <libkern/OSAtomic.h>

#include "MacIOATA.h"
#include "IOATABusCommand.h"


#ifdef DLOG
#undef DLOG
#endif

//#define ATA_DEBUG 1

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

// some day, we'll have an ATA recorder for IOKit

#define ATARecordEventMACRO(type,param,bus,data) 		(void) (type); (void) (param); (void) (bus); (void) (data)


#pragma mark -IOService Overrides -

// 33 data descriptors + NOP + STOP
#define kATAXferDMADesc 33
#define kATAMaxDMADesc kATAXferDMADesc + 2
// up to 256 ATA sectors per transfer
#define kMaxATAXfer	512 * 256


enum {
																/* Command.cmd operations*/
	OUTPUT_MORE					= 0x00000000,
	OUTPUT_LAST					= 0x10000000,
	INPUT_MORE					= 0x20000000,
	INPUT_LAST					= 0x30000000,
	STORE_QUAD					= 0x40000000,
	LOAD_QUAD					= 0x50000000,
	NOP_CMD						= 0x60000000,
	STOP_CMD					= 0x70000000,
	kdbdmaCmdMask				= (long)0xF0000000
};

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

#define super IOATAController

OSDefineMetaClass( MacIOATA, IOATAController )
OSDefineAbstractStructors( MacIOATA, IOATAController )
    OSMetaClassDefineReservedUnused(MacIOATA, 0);
    OSMetaClassDefineReservedUnused(MacIOATA, 1);
    OSMetaClassDefineReservedUnused(MacIOATA, 2);
    OSMetaClassDefineReservedUnused(MacIOATA, 3);
    OSMetaClassDefineReservedUnused(MacIOATA, 4);
    OSMetaClassDefineReservedUnused(MacIOATA, 5);
    OSMetaClassDefineReservedUnused(MacIOATA, 6);
    OSMetaClassDefineReservedUnused(MacIOATA, 7);
    OSMetaClassDefineReservedUnused(MacIOATA, 8);
    OSMetaClassDefineReservedUnused(MacIOATA, 9);
    OSMetaClassDefineReservedUnused(MacIOATA, 10);
    OSMetaClassDefineReservedUnused(MacIOATA, 11);
    OSMetaClassDefineReservedUnused(MacIOATA, 12);
    OSMetaClassDefineReservedUnused(MacIOATA, 13);
    OSMetaClassDefineReservedUnused(MacIOATA, 14);
    OSMetaClassDefineReservedUnused(MacIOATA, 15);
    OSMetaClassDefineReservedUnused(MacIOATA, 16);
    OSMetaClassDefineReservedUnused(MacIOATA, 17);
    OSMetaClassDefineReservedUnused(MacIOATA, 18);
    OSMetaClassDefineReservedUnused(MacIOATA, 19);
    OSMetaClassDefineReservedUnused(MacIOATA, 20);


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

bool 
MacIOATA::init(OSDictionary * properties)
{
    // Initialize instance variables.

    DLOG("MacIOATA::init() starting\n");
   
    if (super::init(properties) == false)
    {
        DLOG("MacIOATA: super::init() failed\n");
        return false;
    }

	isMediaBay = false; // don't know yet
	isBusOnline = true; // if false, it means media bay has ejected.

	_baseAddressMap = 0;
	_dmaBaseMap = 0;
	_DMACursor = 0;
	_descriptors = 0;
	_descriptorsPhysical = 0;
	_devIntSrc = 0;
	_dmaIntSrc = 0;
	_dmaIntExpected = 0;
	_dmaState = MacIOATA::kATADMAInactive;
	_resyncInterrupts = false;

    DLOG("MacIOATA::init() done\n");


    return true;
}



/*---------------------------------------------------------------------------
 *
 *	Override IOService start.
 *
 *	Subclasses should override the start method, call the super::start
 *	first then add interrupt sources and probe their busses for devices 
 *	and create device nubs as needed.
 ---------------------------------------------------------------------------*/

bool 
MacIOATA::start(IOService *provider)
{
     DLOG("MacIOATA::start() begin\n");

 	// call start on the superclass
    if (!super::start( provider))
 	{
        DLOG("MacIOATA: super::start() failed\n");
        return false;
	}

	// Allocate the DMA descriptor area
	if( ! allocDMAChannel() )
	{
        DLOG("MacIOATA:  allocDMAChannel failed\n");
		return false;	
	
	}



	// find the DMA interrupt source and attach it to the command gate
	// if we attach it first, then DMA interrupts will get priority for scheduling.
	if( ! createDMAInterrupt() )
	{
        DLOG("MacIOATA:  createDMAInterrupt failed\n");
		return false;	
	
	}
	// Find the interrupt source and attach it to the command gate
	
	if( ! createDeviceInterrupt() )
	{
        DLOG("MacIOATA:  createDeviceInterrupts failed\n");
		return false;	
	
	}
	


	// enable interrupt sources
	if( !enableInterrupt(0) /*|| !enableInterrupt(1)*/ )
	{
        DLOG("MacIOATA: enable ints failed\n");
		return false;	
	
	}

	// check to see if this is a media-bay socket.
	OSData* nameToMatch  = OSDynamicCast( OSData, provider->getProperty( "AAPL,manually-removable" ) );
	if ( nameToMatch != 0 ) 
	{
		DLOG("MacIOATA got Name property***************************************\n");
		isMediaBay = true;
		
	} else {
	
		DLOG("MacIOATA failed to get Name property!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.\n");
	}


    DLOG("MacIOATA::start() done\n");
    return true;
}

/*---------------------------------------------------------------------------
 *	free() - the pseudo destructor. Let go of what we don't need anymore.
 *
 *
 ---------------------------------------------------------------------------*/
void
MacIOATA::free()
{

	freeDMAChannel();

	if( _baseAddressMap )
		_baseAddressMap->release();
	
	if(_dmaBaseMap)
		_dmaBaseMap->release();
	
	if(_DMACursor)
		_DMACursor->release();

	if(_dmaIntSrc)
		_dmaIntSrc->release();
	
	if(_devIntSrc)
		_devIntSrc->release();
	
	
	
	super::free();
	
	DLOG( "MacIOATA freed.\n");

}


#pragma mark -initialization-


/*---------------------------------------------------------------------------
 *
 *	Initialize the taskfile pointers to the addresses of the ATA registers
 *	in the MacIO chip.
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::configureTFPointers(void)
{
	
	DLOG("MacIOATA configureTFPointers begin\n");


	// get the task file pointers
	_baseAddressMap = _provider->mapDeviceMemoryWithIndex(0);
	
	if( !_baseAddressMap )
	{
		DLOG("MacIOATA no base map\n");
		return false;
	}

	volatile UInt8* baseAddress = (volatile UInt8*)_baseAddressMap->getVirtualAddress();

	if( !baseAddress )
	{
		DLOG("MacIOATA no base address\n");
		return false;
	}


	// setup the taskfile pointers inherited from the superclass
	// this allows IOATAController to scan for drives during it's start()

	_tfDataReg = (volatile UInt16*) (baseAddress + 0x00);

	_tfFeatureReg = baseAddress + 0x10;
	_tfSCountReg = baseAddress + 0x20;
	_tfSectorNReg = baseAddress + 0x30;
	_tfCylLoReg = baseAddress + 0x40;
	_tfCylHiReg = baseAddress + 0x50;
	_tfSDHReg = baseAddress + 0x60;
	_tfStatusCmdReg = baseAddress + 0x70;  
	_tfAltSDevCReg = baseAddress + 0x160;   

	_timingConfigReg = (volatile UInt32*) (baseAddress + 0x200); 
	
	DLOG("MacIOATA baseAdress = %lx\n", baseAddress);

	DLOG("MacIOATA configureTFPointers end\n");
	
	return true;
}

/*---------------------------------------------------------------------------
 *
 *	allocate memory and resources for the DMA descriptors.
 *
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::allocDMAChannel(void)
{

	// map the DMA channel control registers into our address space.

	_dmaBaseMap = _provider->mapDeviceMemoryWithIndex(1);
	
	if( !_dmaBaseMap )
	{
		DLOG("MacIOATA no DMA map\n");
		return false;
	}

	// map into the logical address space of the kernel
	_dmaControlReg = (volatile IODBDMAChannelRegisters*)_dmaBaseMap->getVirtualAddress();

	if( !_dmaControlReg )
	{
		DLOG("MacIOATA no DMA address\n");
		return false;
	}

	// Now allocate a playground for channel descriptors
	
	// under ATA-5 and earlier, ATA commands are allowed a maximum of 256 * 512 byte
	// sectors on a single command. This works out to 1 + 32 * 4096 byte chunks of physical
	// memory if the page size is 4K and the memory is completely fragmented, 
	// so we need a total of 33 descriptors, plus a stop and NO-OP command with 
	// to generate the interrupt. This allows for full transfers without any pauses
	// to regenerate a DMA chain.
	
	// allocate 35 descriptors  33 memory commands + 1 No Op, + 1 Stop.
	
	// IODBDMA-Start panics unless memory is aligned on 0x10
	_descriptors = (IODBDMADescriptor*)IOMallocContiguous( sizeof(IODBDMADescriptor) * kATAMaxDMADesc, 
						0x10, 
						&_descriptorsPhysical );
	
	if(	! _descriptors )
	{
		DLOG("MacIOATA alloc descriptors failed\n");
		return false;
	
	}



	_DMACursor = IODBDMAMemoryCursor::withSpecification(0xFFFE, /*64K - 2*/
                                       					kMaxATAXfer  /* Maybe should be unlimited? */
                                     					/*inAlignment - byte aligned by default*/);

	
	if( ! _DMACursor )
	{
		DLOG("MacIOATA alloc DMACursor failed\n");
		return false;
	}


	// fill the chain with stop commands to initialize it.	
	initATADMAChains(_descriptors);
	
	return true;
}


/*---------------------------------------------------------------------------
 *
 *	deallocate memory and resources for the DMA descriptors.
 *
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::freeDMAChannel(void)
{
	
	if( _descriptors )
	{
		// make sure the engine is stopped
		stopDMA();

		// free the descriptor table.
		IOFreeContiguous( (void*) _descriptors, 
		sizeof(IODBDMADescriptor) * kATAMaxDMADesc);
	}

	return true;
}


/*---------------------------------------------------------------------------
 *
 *	connect the device (drive) interrupt to our workloop
 *
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::createDeviceInterrupt(void)
{
	// create a device interrupt source and attach it to the work loop

	_devIntSrc = IOInterruptEventSource::
	interruptEventSource( (OSObject *)this,
	(IOInterruptEventAction) &MacIOATA::deviceInterruptOccurred, _provider, 0); 


	if( !_devIntSrc || _workLoop->addEventSource(_devIntSrc) )
	{
		DLOG("MacIOATA failed create dev intrpt source\n");
		return false;
	}

	_devIntSrc->enable();

	return true;

}

/*---------------------------------------------------------------------------
 *
 *  static "action" function to connect to our object
 *
 ---------------------------------------------------------------------------*/
void 
MacIOATA::deviceInterruptOccurred(OSObject * owner, IOInterruptEventSource *evtSrc, int count)
{
	MacIOATA* self = (MacIOATA*) owner;

	self->handleDeviceInterrupt();


}

/*---------------------------------------------------------------------------
 *
 *	connect the DMA interrupt to our workloop.
 *
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::createDMAInterrupt(void)
{


	// create a dma interrupt source and attach it to the work loop

	_dmaIntSrc = IOInterruptEventSource::
	interruptEventSource( (OSObject *)this,
	(IOInterruptEventAction) &MacIOATA::dmaInterruptOccurred,
	(IOService *) _provider,
		1	 );  // index 1 is the DMA interrupt.

	if( !_dmaIntSrc || _workLoop->addEventSource(_dmaIntSrc) )
	{
		DLOG("MacIOATA failed create dma intrp source\n");
		return false;
	}

	_dmaIntSrc->enable();
	
	return true;
}

/*---------------------------------------------------------------------------
 *
 * static "action" function to connect to our object
 *
 ---------------------------------------------------------------------------*/
void 
MacIOATA::dmaInterruptOccurred(OSObject * owner, IOInterruptEventSource * evtSrc, int count)
{

	MacIOATA* self = (MacIOATA*) owner;
	
	self->processDMAInterrupt( );	


}



#pragma mark -DMA Interface-

/*---------------------------------------------------------------------------
 *
 * Subclasses should take necessary action to create DMA channel programs, 
 * for the current memory descriptor in _currentCommand and activate the 
 * the DMA hardware
 ---------------------------------------------------------------------------*/
IOReturn
MacIOATA::startDMA( void )
{

	IOReturn err = kATANoErr;

	// first make sure the engine is stopped.
	stopDMA();
	
	
	// reality check the memory descriptor in the current command
	
	// state flag
	_dmaState = kATADMAStarting;
	
	// create the channel commands
	err = createChannelCommands();
	
	if(	err )
	{
	
		DLOG("MacIOATA error createChannelCmds err = %ld\n", (long int)err);
		return err;
	
	}
	
	// fire the engine
	activateDMAEngine();
	
	return err;
	

}




/*---------------------------------------------------------------------------
 * Subclasses should take all actions necesary to safely shutdown DMA engines
 * in any state of activity, whether finished, pending or stopped. Calling 
 * this function must be harmless reguardless of the state of the engine.
 *
 ---------------------------------------------------------------------------*/
 IOReturn
MacIOATA::stopDMA( void )
{

	if(_dmaState != kATADMAInactive)
		shutDownATADMA();
	
	
	_dmaState = kATADMAInactive;
	return kATANoErr;

}

#pragma mark -DMA Implementation-


//----------------------------------------------------------------------------------------
//	Function:		InitATADMAChains
//	Description:	Initializes the chains with STOP commands.
//
//	Input:			Pointer to the DBDMA descriptor area: descPtr
//	
//	Output:			None
//----------------------------------------------------------------------------------------
void	
MacIOATA::initATADMAChains (IODBDMADescriptor* descPtr)
{
	int i;
	
	/* Initialize the data-transfer DBDMA channel command descriptors. */
	/* These descriptors are altered to specify the desired transfer. */
	for (i = 0; i < kATAMaxDMADesc; i++)
	{
		IOMakeDBDMADescriptor(	descPtr,
								kdbdmaStop,
								kdbdmaKeyStream0,
								kdbdmaIntNever,
								kdbdmaBranchNever,
								kdbdmaWaitNever,
								0,
								0);
		descPtr++;
	}
}

/*---------------------------------------------------------------------------
 *
 *	Process the DMA interrupt. We are either done, need more DMA, or an error
 * occurred
 *
 ---------------------------------------------------------------------------*/
void
MacIOATA::processDMAInterrupt (void)
{
	if( isBusOnline == false)
	{
		return;
	}



	if( _dmaIntExpected == false )
	{
		DLOG("MacIOATA DMA Int not expected\n");
		return;
	}
	// Make certain that the DMA state flags are consistent with machine state.
	_dmaIntExpected = false;

	// Bytes transferred by this DMA pass
	IOByteCount   byteCount = 0; 
	
	/* DMA is either finished or has erred. Decide which. */
	switch (_dmaState)
	{
		case kATADMAStatus:
		case kATADMAActive:
			
			//ATARecordEventMACRO(kAIMTapeName,'DMA ','stat','act1');
			
			/* DMA is running and we've gotten an interrupt. Check result. */
			if (scanATADMAChain(&byteCount))
						/* ataDmaState may be altered by ScanATADMAChain. */
				break; /* out of switch. Status is able to be interpreted. */
		default:
			/* We weren't expecting this interrupt, or the hardware shouldn't have sent it. */
			
			ATARecordEventMACRO('MacI','DMA ','Uxpt','Intr');
			shutDownATADMA();
			_dmaState = kATADMAError;
			return;
			break;
	}	
		
	// update the actual byte count for this pass.

	_currentCommand->setActualTransfer(_currentCommand->getActualTransfer() + byteCount);
	
	/* Now the deciphering of the results of the data transfer: A valid interrupt
	 * has occurred for one of 3 cases: 1) Fatal DMA error, 2) end of DMA, 3) more
	 *	DMA required.
	 */

	switch (_dmaState)
	{
		case kATADMAActive:

			/* 
			 * DMA transfer only partially complete. The current chain of descriptors
			 * (CCs) has completed correctly. Additional transfer needed.
			 * Note: Clearing the DBDMA "run" bit has the effect of clearing
			 * internal data FIFOs. To prevent data loss on  multi-pass DMA operations,
			 * DMA must not be stopped until the entire I/O is complete.
			 */
			
			if (createChannelCommands())
			{
				ATARecordEventMACRO('MacI','DMA ','chan','err1');
				/* An OSErr has been reported - likely from a memory manager call. */
				_dmaState = kATADMAError; /* Take the blame for now. */
				return;
			}
				

			ATARecordEventMACRO('MacI',' DMA','rest','art.');
			//start the engine for another DMA pass.
			
			activateDMAEngine();
			
			break;
		
		case kATADMAStatus:
			/* Flag the DMA transfer as completed. */
			ATARecordEventMACRO('MacI','DMA ','stat','good');
			_dmaState = kATADMAComplete;
			stopDMA ();

			// may need some handling here in the event that the DMA int arrives
			// after the device interrupt.
			
			break;

		case kATADMAError:
			/*
			 *	The drive is expecting additional data transfer, but the DMA has croaked. 
			 *	For now, let the device interrupt timeout happen.
			 */
			ATARecordEventMACRO('MacI','DMA ','chan','err2');
			
			stopDMA ();
			break;
		default:
			break;
	}


	// if we meet a certain edge case condition, the drive may have emptied it's data 
	// into the controller's fifo, but the DMA engine has not written data into system 
	// memory yet, the drive may assert IRQ prior to the DMA engine. In this case, we defer the
	// handling of the device interrupt until the DMA engine signals completion.
	if( _resyncInterrupts == true )
	{
		_resyncInterrupts = false;
		handleDeviceInterrupt();
	}

}


void
MacIOATA::stopDMAEngine(void)
{

	/* Not doing DMA any more. Set up to ignore any DMA interrupts. */
	/* Leave other bits intact for error detection. */


	IODBDMAStop(_dmaControlReg);

	//IOSetDBDMAChannelControl( _dmaControlReg, IOClearDBDMAChannelControlBits(kdbdmaRun));
	
	//while (IOGetDBDMAChannelStatus(_dmaControlReg) & kdbdmaActive)
	//	{;}


}



/*----------------------------------------------------------------------------------------
//	Function:		activateDMAEngine
//	Description:	Activate the DBDMA on the ATA bus associated with current device.
					engine will begin executing the command chain already programmed.
//	Input:			None
//	Output:			None
//----------------------------------------------------------------------------------------*/

void			
MacIOATA::activateDMAEngine(void)
{

	if( IOGetDBDMAChannelStatus( _dmaControlReg) & kdbdmaActive )
	{
		/* For multiple DMA chain execution, don't stop the DMA or the FIFOs lose data.*/
		/* If DMA is active already (stray from an error?), shut it down cleanly. */
		shutDownATADMA();
	}
	

    IOSetDBDMACommandPtr( _dmaControlReg, (unsigned int) _descriptorsPhysical);


	/* Blastoff! */
	//ATARecordEventMACRO(kAIMTapeName,' dma','true','StDM');
	_dmaIntExpected = true;
	
	// IODBDMAStart will flush the FIFO by clearing the run-bit, causing multiple chain execution 
	// to fail by losing whatever bytes may have accumulated in the ATA fifo.
	
	//IODBDMAStart(_dmaControlReg, (volatile IODBDMADescriptor *)_descriptorsPhysical);
	IOSetDBDMAChannelControl(_dmaControlReg, IOSetDBDMAChannelControlBits( kdbdmaRun | kdbdmaWake));
}

void	
MacIOATA::shutDownATADMA (void)
{

	//ATARecordEventMACRO(kAIMTapeName,'Shut','Down',' DMA');

	// Disable interrupts while we flush the chain
	_dmaIntSrc->disable();
	stopDMAEngine();

	// Make certain that the DMA state flags are consistent with machine state.
	_dmaIntExpected = false;

	// set the state semaphore 
	_dmaState = kATADMAInactive;

	// Reenable interrupts

	_dmaIntSrc->enable();

}


/*---------------------------------------------------------------------------
 *
 *	create the DMA channel commands.
 *
 *
 ---------------------------------------------------------------------------*/
IOReturn
MacIOATA::createChannelCommands(void)
{
	IOMemoryDescriptor* descriptor = _currentCommand->getBuffer();

	if( ! descriptor )
	{
	
		DLOG("drvMacIO nil buffer!\n");
		return -1;
	}

	// calculate remaining bytes in this transfer

	IOByteCount bytesRemaining = _currentCommand->getByteCount() 
								- _currentCommand->getActualTransfer();



	// calculate position pointer
	IOByteCount xfrPosition = _currentCommand->getPosition() + 
							_currentCommand->getActualTransfer();

	IOByteCount  transferSize = 0; 

	// have the DMA cursor fill out the addresses in the CC table
	// it will return the number of descriptors consumed.

	UInt32 segmentCount = _DMACursor->getPhysicalSegments(
										descriptor,
				       					xfrPosition,
				       					_descriptors,
				     					kATAXferDMADesc,
				     					bytesRemaining,  // limit to the requested number of bytes in the event the descriptors is larger
				       					&transferSize);
				       					
	if( transferSize > bytesRemaining)
	{
		DLOG("drvMacIO DMA too long!!!\n");
		return -1;	
	
	}

	if( segmentCount == 0)
	{
		DLOG("drvMacIO seg count 0!!!\n");
		return -1;	
	
	}


	// check if the xfer satisfies the needed size
	if( transferSize < bytesRemaining )
	{
		// indicate we need to do more DMA when the interrupt happens
	
		_dmaState = kATADMAActive;
		DLOG("MacIOATA will make two passes\n");
	
	} else {
		
		// transfer is satisfied and only need to check status on interrupt.
		_dmaState = kATADMAStatus;
		DLOG("MacIOATA will make one pass\n");
	
	}

	UInt32 command = kdbdmaOutputMore;

	if( _currentCommand->getFlags() & mATAFlagIORead)
	{
		command = kdbdmaInputMore;
	
	}

	DLOG("MacIOATA making CC chain for %ld segs for xfersize %ld\n", segmentCount, transferSize);

	// now walk the descriptor chain and insert the commands
	for( UInt32 i = 0; i < segmentCount; i++)
	{
	
		IODBDMADescriptor* currDesc = & (_descriptors[i]);
		
		UInt32 addr = IOGetCCAddress(currDesc);
		UInt32 count =  IOGetCCOperation(currDesc) & kdbdmaReqCountMask;
		OSSynchronizeIO();
		
		IOMakeDBDMADescriptor(currDesc,
								command,
								kdbdmaKeyStream0,
								kdbdmaIntNever,
								kdbdmaBranchNever,
								kdbdmaWaitNever,
								count,
								addr);
	
		DLOG("macIOATA desc# %ld at %x \n", i, currDesc );
		DLOG("addr = %lx  count = %lx  \n", addr, count );
		
		DLOG("%lx  %lx  %lx  %lx\n", currDesc->operation, currDesc->address ,currDesc->cmdDep ,currDesc->result );
		
	}

	// insert a NOP + interrupt. after the last data command
	IOMakeDBDMADescriptor(&(_descriptors[segmentCount]),
						kdbdmaNop,
						kdbdmaKeyStream0,
						kdbdmaIntAlways,
						kdbdmaBranchNever,
						kdbdmaWaitNever,
						0,
						0);


	// insert a stop after the NOP command
	IOMakeDBDMADescriptor(&(_descriptors[segmentCount + 1]),
						kdbdmaStop,
						kdbdmaKeyStream0,
						kdbdmaIntNever,
						kdbdmaBranchNever,
						kdbdmaWaitNever,
						0,
						0);


	// chain is now ready for execution.

	return kATANoErr;

}

bool	
MacIOATA::scanATADMAChain (IOByteCount* byteCount)
{
	volatile IODBDMADescriptor*	descPtr 		= _descriptors; 
	bool				validState		= true;
	
	*byteCount = 0;	/* No bytes confirmed this DMA pass. */

	UInt32 descIndex = 0;
	/*
	 *	Parse the chain for completion status. Normal completion is
	 *	indicated by all expected CC status words contain just the
	 *	"run" and "active" bits, indicating that the DMA has executed the
	 * 	chain element and updated the status. 
	 */

	 while ((IOGetCCOperation(descPtr) & kdbdmaCmdMask) != STOP_CMD)
	 {
		/* For speed, check the normal case - "run" and "active" bits  and residual count == 0 */
		if ( (IOGetCCResult(descPtr) & 0x8C000000) == 0x84000000 )
		{
			/* The DMA has completed this channel command.*/
			
			*byteCount += (IOGetCCOperation(descPtr) & 0x0000FFFF); // low 16 bits are request count
			descPtr++;  /* Advance pointer to the next DMA command */
			descIndex++;
			continue;
		}

		/* Get here if something's wrong with DMA execution. See whether hardware 
		 * is telling us something directly, or whether something amiss is noted
		 * in the status written to memory.
		 */
		
		if ((IOGetDBDMAChannelStatus(_dmaControlReg) & kdbdmaStatusDead) || ((IOGetCCResult(descPtr) & kdbdmaStatusRun)))
		{
			/*
			 *	Dead means fatal DMA error - hardware claims it failed. Report error 
			 *	if dead or if the DMA was running when the interrupt occurred.
			 */
			
			DLOG("MacIOATA DMA error state\n");
			DLOG("MacIOATA desc# %ld byte count %ld\n",descIndex, *byteCount); 
			DLOG("ChanStat= %lx   CCResult= %lx\n", IOGetDBDMAChannelStatus(_dmaControlReg), IOGetCCResult(descPtr) );
			
			_dmaState = kATADMAError;
			break;	/* out of do/while loop */
		
		}  else {
		
 			/*
			 *	The DMA status indicates that command descriptor execution is not
			 *	responsible for the interrupt. Another case to ignore.
			 */
		
			DLOG("MacIOATA DMA Invalid state\n");
			validState = false;
			break; /* Ignore the interrupt */
		}
				 
		
				 
	}	/* end while !STOP_CMD */
		
	/* Return to caller, indicating whether dmaState is valid. */
	DLOG("MacIOATA desc# %ld byte count %ld\n",descIndex, *byteCount); 
	
	return validState;
} 



/*---------------------------------------------------------------------------
 *
 *	handleDeviceInterrupt - overriden here so we can make sure that the DMA has
 * processed in the event the interrupts arrive out of order for some reason.
 *
 ---------------------------------------------------------------------------*/
IOReturn
MacIOATA::handleDeviceInterrupt(void)
{
	if( isBusOnline == false)
	{
		return kIOReturnOffline;
	}
	// if we meet a certain edge case condition, the drive may have emptied it's data 
	// into the controller's fifo, but the DMA engine has not written data into system 
	// memory yet, the drive may assert IRQ prior to the DMA engine. In this case, we defer the
	// handling of the device interrupt until the DMA engine signals completion.

	if( _dmaIntExpected == true 
		&& _currentCommand->state == IOATAController::kATAStatus)	
	{	
		DLOG("MacIOATA, DMA int out of order\n");

		// read the device's status register in order to clear the IRQ assertion
		volatile UInt8 status = *_tfStatusCmdReg;
		OSSynchronizeIO();
		//if an error or check condition bit is set then DMA isn't going to happen at this 
		// point. go ahead and allow the superclass to process it.
		if( status & 0x01 )
		{
			return super::handleDeviceInterrupt();
		}
		_resyncInterrupts = true;
		return kATANoErr;
	}

	return super::handleDeviceInterrupt();
	
}



/*---------------------------------------------------------------------------
 *
 *	handleDeviceInterrupt - overriden here to allow for reporting of DMA errs
 *
 ---------------------------------------------------------------------------*/
IOReturn	
MacIOATA::asyncStatus(void)
{

	IOReturn err = super::asyncStatus();
	
	if( _dmaState == kATADMAError)
	{
	
		err = kATADMAErr;
	
	}
	return err;

}


/*---------------------------------------------------------------------------
 *
 *	handleDeviceInterrupt - overriden here to allow clean up of DMA resyncInterrupt flag
 *
 ---------------------------------------------------------------------------*/
void
MacIOATA::handleTimeout( void )
{
	if( isBusOnline == false)
	{
		return;
	}

	_resyncInterrupts = false;
	super::handleTimeout();

}

// media bay support

IOReturn 
MacIOATA::message (UInt32 type, IOService* provider, void* argument)
{

	switch( type )
	{
		case kATARemovedEvent:
		DLOG( "MacIOATA got removed event.\n");
		// mark the bus as dead.
		if(isBusOnline == true)
		{
			isBusOnline = false;
			// lock the queue, don't dispatch immediates or anything else.
			_queueState = IOATAController::kQueueLocked;
			// disable the interrupt source(s) and timers
			_dmaIntSrc->disable();
			_devIntSrc->disable();
			stopTimer();
			
			_workLoop->removeEventSource(_dmaIntSrc);
			_workLoop->removeEventSource(_devIntSrc);
			_workLoop->removeEventSource(_timer);
			
			//_provider->unregisterInterrupt(0);
			//_provider->unregisterInterrupt(1);
			// flush any commands in the queue
			handleQueueFlush();
			
			// if there's a command active then call through the command gate
			// and clean it up from inside the workloop.
			// 

			if( _currentCommand != 0)
			{
			
				DLOG( "MacIOATA Calling cleanup bus.\n");
				
					_cmdGate->runAction( (IOCommandGate::Action) 
						&MacIOATA::cleanUpAction,
            			0, 			// arg 0
            			0, 		// arg 1
            			0, 0);						// arg2 arg 3

			
			
			}
			_workLoop->removeEventSource(_cmdGate);
			DLOG( "MacIOATA notify the clients.\n");			
			terminate();
			
		}
		break;
	
	
		default:
		
		DLOG( "MacIOATA got some other event = %d\n", (int) type);
		return super::message(type, provider, argument);
		break;
	}


	return kATANoErr;

}


/*---------------------------------------------------------------------------
 *
 *
 *
 *
 ---------------------------------------------------------------------------*/
IOReturn
MacIOATA::handleQueueFlush( void )
{

	UInt32 savedQstate = _queueState;

	_queueState = IOATAController::kQueueLocked;

	IOReturn errPerCommand = kIOReturnError;

	if( isBusOnline == false )
	{
	
		errPerCommand = kIOReturnOffline;
	
	}

	IOATABusCommand* cmdPtr = 0;

	while( cmdPtr = dequeueFirstCommand() )
	{
	
		cmdPtr->setResult(errPerCommand);
		cmdPtr->executeCallback();
	
	}

	_queueState = savedQstate;

	return kATANoErr;
}

/*---------------------------------------------------------------------------
 *
 *
 *
 *
 ---------------------------------------------------------------------------*/
bool
MacIOATA::checkTimeout( void )
{
	if( isBusOnline == false )
	{
		// signal a timeout if we are within the workloop
		return true;
	
	} 

	return super::checkTimeout();
}

/*---------------------------------------------------------------------------
 *
 *	The main call which puts something on the work loop
 *
 ---------------------------------------------------------------------------*/

IOReturn 
MacIOATA::executeCommand(IOATADevice* nub, IOATABusCommand* command)
{
	if( isBusOnline == false)
	{
		return kIOReturnOffline;
	}
		
	return super::executeCommand(nub, command);

}


// called through the commandGate when I get a notification that a media bay has gone away

void
MacIOATA::cleanUpAction(OSObject * owner,
                                               void *     arg0,
                                               void *     arg1,
                                               void *  /* arg2 */,
                                               void *  /* arg3 */)
{


	MacIOATA* self = (MacIOATA*) owner;
	self->cleanUpBus();
}


void
MacIOATA::cleanUpBus(void)
{
	if( _currentCommand != 0)
	{
	
		_currentCommand->setResult(kIOReturnOffline);
		_currentCommand->executeCallback();
		_currentCommand = 0;
	}

}
IOReturn
MacIOATA::handleBusReset(void)
{
//	if( _devIntSrc )
//		_devIntSrc->disable();
		
	IOReturn result = super::handleBusReset();
	
//	if( _devIntSrc )
//		_devIntSrc->enable();

	return result;

}

#endif // __ppc__