Source to iokit/Families/IOATAHDDrive/IOATAHDCommand.cpp


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1998-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@
 */
/*
 * Copyright (c) 1999 Apple Computer, Inc.  All rights reserved. 
 *
 * IOATAHDCommand.cpp - Performs ATA command processing.
 *
 * HISTORY
 * Aug 27, 1999	jliu - Ported from AppleATADrive.
 */

#include <IOKit/assert.h>
#include <IOKit/IOSyncer.h>
#include <IOKit/storage/ata/IOATAHDDrive.h>

// Enable this define to generate debugging messages.
// #define DEBUG_LOG 1

// --------------------------------------------------------------------------
// Set the device timings.

bool
IOATAHDDrive::selectTimingProtocol(ATATimingProtocol protocol)
{
	bool				ret;
	UInt8				ataReadCmd;
	UInt8				ataWriteCmd;
	
	if (protocol >= ataMaxTimings)
	{
		// If protocol override is invalid, get the list of supported timing
		// protocols from the device, and use the 'best' one. If the device
		// cannot report the available protocols, default to PIO mode.
	
		ret = _ataDevice->getTimingsSupported(&protocol);
		if (!ret)
		{
			IOLog("%s: getTimingsSupported() error\n", getName());
			protocol = ataTimingPIO;
		}
	}

	// IOLog("%s: timing protocols: %08x\n", getName(), protocol);
	
	if (protocol & (ataTimingUltraDMA66 | ataTimingUltraDMA33 | ataTimingDMA)) 
	{
		if (protocol & ataTimingUltraDMA66)
		{
			IOLog("%s: Using U-DMA/66 transfers\n", getName());
			protocol = ataTimingUltraDMA66;
		}
		else if (protocol & ataTimingUltraDMA33)
		{
			IOLog("%s: Using U-DMA/33 transfers\n", getName());
			protocol = ataTimingUltraDMA33;
		}
		else
		{
			IOLog("%s: Using DMA transfers\n", getName());
			protocol = ataTimingDMA;
		}

		ataReadCmd  = kIOATACommandReadDMA;
		ataWriteCmd = kIOATACommandWriteDMA;		
		selectCommandProtocol(true);
	}
	else
	{
		IOLog("%s: Using PIO transfers\n", getName());
		protocol    = ataTimingPIO;
		ataReadCmd  = kIOATACommandReadPIO;
		ataWriteCmd = kIOATACommandWritePIO;
		selectCommandProtocol(false);
	}

	ret = _ataDevice->selectTiming(protocol);
	if (!ret) {
		IOLog("%s: selectTiming() failed\n", getName());
	}
	else {
		_timingProtocol = protocol;
		_ataReadCmd     = ataReadCmd;
		_ataWriteCmd    = ataWriteCmd;
	}

	return ret;
}

// --------------------------------------------------------------------------
// Returns the Command protocol to use (e.g. ataProtocolPIO, ataProtocolDMA).

void
IOATAHDDrive::selectCommandProtocol(bool useDMA)
{
	if (useDMA)
		_ataProtocol = ataProtocolDMA;
	else
		_ataProtocol = ataProtocolPIO;
}

// --------------------------------------------------------------------------
// Setup an ATATaskFile from the parameters given, and write the taskfile
// to the ATATaskfile structure pointer provided.
//
// taskfile - pointer to a taskfile structure.
// protocol - An ATA transfer protocol (ataProtocolPIO, ataProtocolDMA, etc)
// command  - ATA command byte.
// block    - Initial transfer block.
// nblks    - Number of blocks to transfer.

void
IOATAHDDrive::setupReadWriteTaskFile(ATATaskfile * taskfile,
                                     ATAProtocol   protocol,
                                     UInt8         command,
                                     UInt32        block,
                                     UInt32        nblks)
{
	taskfile->protocol = protocol;

	// Mask of all taskfile registers that shall contain valid
	// data and should be written to the hardware registers.
	//
	taskfile->regmask  = ATARegtoMask(ataRegSectorNumber)	|
                         ATARegtoMask(ataRegCylinderLow)	|
                         ATARegtoMask(ataRegCylinderHigh)	|
						 ATARegtoMask(ataRegDriveHead)		|
						 ATARegtoMask(ataRegSectorCount)	|
						 ATARegtoMask(ataRegCommand);

	taskfile->resultmask = 0;

	taskfile->ataRegs[ataRegSectorNumber] = block & 0x0ff;
	taskfile->ataRegs[ataRegCylinderLow]  = (block >> 8) & 0xff;
	taskfile->ataRegs[ataRegCylinderHigh] = (block >> 16) & 0xff;
	taskfile->ataRegs[ataRegDriveHead]    = ((block >> 24) & 0x0f) |
                                            ataModeLBA | (_unit << 4);  
	taskfile->ataRegs[ataRegSectorCount]  = (nblks == kIOATAMaxBlocksPerXfer) ? 
                                            0 : nblks;
    taskfile->ataRegs[ataRegCommand]      = command;
}

// --------------------------------------------------------------------------
// Allocate and return an IOATACommand that is initialized to perform
// a read/write operation.
//
// buffer   - IOMemoryDescriptor object describing this transfer.
// block    - Initial transfer block.
// nblks    - Number of blocks to transfer.

IOATACommand *
IOATAHDDrive::ataCommandReadWrite(IOMemoryDescriptor * buffer,
                                  UInt32               block,
                                  UInt32               nblks)
{
	ATATaskfile taskfile;
	bool        isWrite;

	assert(buffer);

	IOATACommand * cmd = allocateCommand();
	if (!cmd) return 0;		// error, command allocation failed.

	isWrite = (buffer->getDirection() == kIODirectionOut) ?
	          true : false;

#ifdef DEBUG_LOG
	IOLog("%s: ataCommandReadWrite %08x (%d) %s %d %d\n",
		getName(),
		buffer,
		buffer->getLength(),
		isWrite ? "WR" : "RD",
		block,
		nblks);
#endif

#if 0	// used for testing - force PIO mode
	setupReadWriteTaskFile(&taskfile,
	                       ataProtocolPIO,
	                       isWrite ? kIOATACommandWritePIO : 
						   kIOATACommandReadPIO,
	                       block,
	                       nblks);
#else

	// 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(&taskfile,
	                       _ataProtocol,
	                       isWrite ? _ataWriteCmd : _ataReadCmd,
	                       block,
	                       nblks);
#endif

	// Get a pointer to the client data buffer, and record parameters
	// which shall be later used by the completion routine.
	//
	IOATAClientData * clientData = ATA_CLIENT_DATA(cmd);
	assert(clientData);

	clientData->buffer  = buffer;

	cmd->setTaskfile(&taskfile);

	cmd->setPointers(buffer,                /* (IOMemoryDescriptor *) */
                     buffer->getLength(),   /* transferCount (bytes) */
                     isWrite);              /* isWrite */

	return cmd;
}

// --------------------------------------------------------------------------
// Allocate and return a ATA SetFeatures command.

IOATACommand *
IOATAHDDrive::ataCommandSetFeatures(UInt8 features,
                                    UInt8 SectorCount,
                                    UInt8 SectorNumber,
                                    UInt8 CylinderLow,
                                    UInt8 CyclinderHigh)
{
	ATATaskfile  taskfile;

	IOATACommand * cmd = allocateCommand();
	if (!cmd) return 0;		// error, command allocation failed.

	taskfile.protocol   = ataProtocolSetRegs;

	taskfile.regmask    = ATARegtoMask(ataRegSectorNumber)  |
                          ATARegtoMask(ataRegCylinderLow)   |
                          ATARegtoMask(ataRegCylinderHigh)  |
                          ATARegtoMask(ataRegDriveHead)     |
                          ATARegtoMask(ataRegSectorCount)   |
                          ATARegtoMask(ataRegCommand);

	taskfile.resultmask = ATARegtoMask(ataRegError) |
                          ATARegtoMask(ataRegStatus);

	taskfile.ataRegs[ataRegFeatures]     = features;
	taskfile.ataRegs[ataRegSectorNumber] = SectorNumber;
	taskfile.ataRegs[ataRegCylinderLow]  = CylinderLow;
	taskfile.ataRegs[ataRegCylinderHigh] = CyclinderHigh;
    taskfile.ataRegs[ataRegDriveHead]    = ataModeLBA | (_unit << 4);
	taskfile.ataRegs[ataRegSectorCount]  = SectorCount;
    taskfile.ataRegs[ataRegCommand]      = kIOATACommandSetFeatures;

	cmd->setTaskfile(&taskfile);

	return cmd;
}

// --------------------------------------------------------------------------
// This method is responsible for calling the client's completion routine
// for an async command.

void
IOATAHDDrive::completionCallback(IOService *          target,
                                 gdCompletionFunction action,
                                 void *               param,
                                 UInt32               bytesTransferred,
                                 IOReturn             status)
{
	(*action)(target, param, bytesTransferred, status);
}

// --------------------------------------------------------------------------
// This routine is called by our provider when a command processing has
// completed. We currently do not use the 'refcon' argument.

void
IOATAHDDrive::completionHandler(IOService *     device,
                                IOATACommand *  cmd,
                                void *          refcon)
{
	assert(cmd && device);

	ATAResults  	  results;
	IOATAClientData * clientData = ATA_CLIENT_DATA(cmd);

	assert(clientData);

	if ((cmd->getResults(&results) != ataReturnNoError) &&
		(clientData->maxRetries-- > 0) &&
		enqueueCommand(cmd))
		return;

#if 0
	// Force command retry to test retry logic.
	// Controller will reset the IOMemoryDescriptor's position, right?
	//
	cmd->getResults(&results);
	if (clientData->maxRetries-- > 2) {		
		enqueueCommand(cmd);
		return;
	}
#endif

#ifdef DEBUG_LOG
	IOLog("%s: completionHandler %08x %08x %08x %08x %d\n",
		getName(), device, cmd, refcon, getIOReturn(&results),
		results.bytesTransferred);
#endif

	// Return ATAReturnCode for sync commands.
	//
	clientData->returnCode = results.returnCode;

	if (clientData->isSync) {
		// For sync commands, unblock the client thread.
		//
		assert(clientData->completion.syncLock);
		clientData->completion.syncLock->signal();	// unblock the client.
	}
	else {
		// The TAP fields must be set for an async command.
		// Signal the completion routine that the request has been completed.
		//
		assert(clientData->completion.async.action &&
			   clientData->completion.async.target);

		completionCallback(clientData->completion.async.target,
                           clientData->completion.async.action,
                           clientData->completion.async.param,
                           results.bytesTransferred,
                           getIOReturn(&results));
	}

	// Release the IOMemoryDescriptor.
	//
	if (clientData->buffer)
		clientData->buffer->release();

	// Command processing is complete, release the command object.
	//
	cmd->release();
}

// --------------------------------------------------------------------------
// Issue a synchronous ATA command.

ATAReturnCode
IOATAHDDrive::syncExecute(IOATACommand *  cmd,       /* command object */
                          UInt32          timeout,   /* timeout in ms */
                          UInt            retries)   /* max retries */
{
	IOATAClientData * clientData = ATA_CLIENT_DATA(cmd);

	// Bump the retain count on the command. The completion handler
	// will decrement the retain count.
	//
	cmd->retain();

	// Set timeout and register the completion handler.
	//
	cmd->setTimeout(timeout);
        cmd->setCallback(this,
                         (ATACallback) &IOATAHDDrive::completionHandler,
                         (void *) 0);

	// Increment the retain count on the IOMemoryDescriptor.
	// Release when the completion routine gets called.
	//
	if (clientData->buffer)
		clientData->buffer->retain();

	// Set the max retry count. If retry count is 0, then the command shall
	// not be retried if an error occurs.
	//
	clientData->maxRetries = retries;
	clientData->completion.syncLock = IOSyncer::create();
	clientData->isSync = true;

	cmd->execute();
//	enqueueCommand(cmd);	// queue command and kick off workloop.

	// Block client thread on lock until the completion handler
	// receives an indication that the processing is complete.
	//
        clientData->completion.syncLock->wait();

	return clientData->returnCode;
}

// --------------------------------------------------------------------------
// Issue an asynchronous ATA command.

ATAReturnCode
IOATAHDDrive::asyncExecute(IOATACommand *       cmd,      /* command object */
                           IOService *          target,
                           gdCompletionFunction action,
                           void *               param,
                           UInt32               timeout,  /* timeout in ms */
                           UInt                 retries)  /* max retries */
{
	IOATAClientData * clientData = ATA_CLIENT_DATA(cmd);

	// For async commands, the completion target/action must have
	// not be 0.
	//
	if ((target && action) == 0)
		return ataReturnNotSupported;

	// Bump the retain count on the command. The completion handler
	// will decrement the retain count.
	//
	cmd->retain();

	// Set timeout and register the completion handler.
	//
	cmd->setTimeout(timeout);
        cmd->setCallback(this,
                         (ATACallback) &IOATAHDDrive::completionHandler,
                         (void *) 0);

	// Increment the retain count on the IOMemoryDescriptor.
	// Release when the completion routine gets called.
	//
	if (clientData->buffer)
		clientData->buffer->retain();

	// Set the max retry count. If retry count is 0, then the command shall
	// not be retried if an error occurs.
	//
	clientData->maxRetries = retries;
	clientData->isSync     = false;
	
	clientData->completion.async.target = target;
	clientData->completion.async.action = action;
	clientData->completion.async.param  = param;

	return (cmd->execute() ? ataReturnNoError : ataReturnNoResource);
}

// --------------------------------------------------------------------------
// Add a command to the retry/sync queue.

bool
IOATAHDDrive::enqueueCommand(IOATACommand * cmd)
{
	// Add command to the queue.
	//
	_retryQueue->enqueueCommand(cmd);

	// wake up the workloop.
	//
	_evSource->interruptOccurred(0, 0, 0);

	return true;
}

// --------------------------------------------------------------------------
// Dequeues commands from the queue and executes them.

void
IOATAHDDrive::dequeueCommands(IOInterruptEventSource * /*source*/,
                              int /*count*/)
{
	_retryQueue->executeCommands();
}

// --------------------------------------------------------------------------
// Allocate an IOATACommand object.

IOATACommand *
IOATAHDDrive::allocateCommand()
{
	IOATACommand * cmd;

	cmd = _ataDevice->allocCommand(sizeof(IOATAClientData));
	return cmd;
}

// ==========================================================================
// IOATACommandQueue
// ==========================================================================

#define	super OSObject
OSDefineMetaClassAndStructors(IOATACommandQueue, OSObject)

// --------------------------------------------------------------------------
// Static member function which allocates and returns a queue object.

IOATACommandQueue * IOATACommandQueue::commmandQueue()
{
	IOATACommandQueue * queue = new IOATACommandQueue;
	
	if (queue && !queue->init()) {
		queue->release();
		queue = 0;
	}
	
	return queue;
}

// --------------------------------------------------------------------------
// Initializes the queue object.

bool IOATACommandQueue::init()
{
	_enabled = false;
	_cmdLock = 0;
	_qLock   = 0;

	if (!super::init())
		return false;

	// Initialize the queue.
	//
	queue_init(&_queue);

	_cmdLock = IORecursiveLockAlloc();
	if (!_cmdLock)
		return false;

	// Queue access is protected by a spinlock.
	//
	_qLock = IOSimpleLockAlloc();
	if (!_qLock)
		return false;
	
	return true;
}

// --------------------------------------------------------------------------
// Frees the queue object.

void IOATACommandQueue::free()
{
	if (_qLock) {
		IOSimpleLockFree(_qLock);
		_qLock = 0;
	}

	if (_cmdLock) {
		IORecursiveLockFree(_cmdLock);
		_cmdLock = 0;
	}
	
	//
	// What about IOATACommand objects in the queue?
	//
	
	super::free();
}

// --------------------------------------------------------------------------
// Dequeues and executes all IOATACommand objects in the queue.

void IOATACommandQueue::executeCommands()
{
	IOATAClientData * clientData;
	IOATACommand * 	  cmd;

        IORecursiveLockLock(_cmdLock);

	while (_enabled && !queue_empty(&_queue)) {
		IOSimpleLockLock(_qLock);
		queue_remove_first(&_queue, clientData, IOATAClientData *, link);
		IOSimpleLockUnlock(_qLock);
		
		assert(clientData);

		cmd = clientData->command;

#ifdef DEBUG_LOG
		IOLog("cmd:%08x clientData:%08x ATA_CLIENT_DATA(cmd):%08x\n",
			cmd, clientData, ATA_CLIENT_DATA(cmd));
#endif

		assert(cmd && (ATA_CLIENT_DATA(cmd) == clientData));

		cmd->execute();
	}

        IORecursiveLockUnlock(_cmdLock);
}

// --------------------------------------------------------------------------
// Enqueues an IOATACommand object to the queue.
// Returns true if the command specifed was enqueued.

bool IOATACommandQueue::enqueueCommand(IOATACommand * cmd)
{
	assert(cmd);

	IOATAClientData * clientData = ATA_CLIENT_DATA(cmd);

	// Make sure the clientData to IOATACommand linkage is correct.
	//
	clientData->command = cmd;

#ifdef DEBUG_LOG
	IOLog("enqueueCommand: %08x\n", (UInt) cmd);
#endif

	IOSimpleLockLock(_qLock);
	queue_enter(&_queue, clientData, IOATAClientData *, link);
	IOSimpleLockUnlock(_qLock);

	return true;
}

// --------------------------------------------------------------------------
// Enable/disable the queue.

void IOATACommandQueue::setEnabled(bool enable)
{
        IORecursiveLockLock(_cmdLock);
	_enabled = enable;
        IORecursiveLockUnlock(_cmdLock);
}