Source to bsd/dev/ppc/drvPPCATA/AtapiCntCmds.m


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

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 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 1.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.apple.com/publicsource 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

// Copyright 1997 by Apple Computer, Inc., all rights reserved.
/*
 * Copyright (c) 1994-1997 NeXT Software, Inc.  All rights reserved. 
 *
 * AtapiCntCmds.m - ATAPI command implementation for ATA interface. 
 *
 * HISTORY 
 *
 */

#import "AtapiCntCmds.h"
#import "IdeCntInit.h"
#import "IdeCntCmds.h"
#import "IdeCntDma.h"

@implementation IdeController(ATAPI)

#define ATAPI_MAX_WAIT_FOR_NOTBUSY		(5 * 1000)

/*
 * We could use the ATA version instead of rolling our own here. 
 */
- (atapi_return_t)atapiWaitForNotBusy
{
    int i;
    unsigned char status;

    for (i = 0; i < ATAPI_MAX_WAIT_FOR_NOTBUSY; i++)  
    {
	status = inb(_ideRegsAddrs.status);
	
	if (!(status & BUSY)) 
        {
	    return IDER_SUCCESS;
	}
	IOSleep(1);
    }
    return IDER_TIMEOUT;
}

/*
 * Before sending a packet to ATAPI device, we test if BSY == 0 and DRQ == 0. 
 */
#define ATAPI_MAX_WAIT_READY_FOR_PACKET		(5 * 1000)

- (atapi_return_t)atapiWaitReadyForPacket
{
    int i;
    unsigned char status;

    for (i = 0; i < ATAPI_MAX_WAIT_READY_FOR_PACKET; i++)  
    {
	status = inb(_ideRegsAddrs.status);
	
	if (!(status & BUSY) && !(status & DREQUEST)) 
        {
	    return IDER_SUCCESS;
	}
	IOSleep(1);	
    }
    
    return IDER_DEV_NOT_READY;
}

-(void)printAtapiInfo:(ideIdentifyInfo_t *)ideIdentifyInfo 
			Device:(unsigned char)unit
{
    int 		i;
    char 		name[50];
    char 		firmware[9];
    atapiGenConfig_t 	*atapiGenConfig;
    char 		*protocolTypeStr, *deviceTypeStr, *cmdDrqTypeStr;
    char 		*removableStr, *cmdPacketSizeStr;

    /*
     * Print drive name with firmware revision number after doing byte swaps. 
     */
    for (i = 0; i < 20; i++)	
    {
	name[2*i] = ideIdentifyInfo->modelNumber[2*i+1];
	name[2*i+1] = ideIdentifyInfo->modelNumber[2*i];
    }
    name[40] = '\0';
    
    for (i = 0; i < 4; i++)	
    {
	firmware[2*i] = ideIdentifyInfo->firmwareRevision[2*i+1];
	firmware[2*i+1] = ideIdentifyInfo->firmwareRevision[2*i];
    }
    firmware[8] = '\0';
    
    for (i = 38; i >= 0; i--)	{
	if (name[i] != ' ')
	    break;
    }
    strcpy(name+i+2, firmware);
    
    /*
     * This information should be printed since it is not duplicated
     * elsewhere. 
     */
    atapiGenConfig = (atapiGenConfig_t *) &ideIdentifyInfo->genConfig;
    
    if (atapiGenConfig->protocolType == 0 || atapiGenConfig->protocolType == 1)
    	protocolTypeStr = "ATA";
    else if (atapiGenConfig->protocolType == 2)
    	protocolTypeStr = "ATAPI";
    else 
    	protocolTypeStr = "UNKNOWN PROTOCOL";
	
    if (atapiGenConfig->deviceType == 0)
    	deviceTypeStr = "DIRECT ACCESS";
    else if (atapiGenConfig->deviceType == 5)
    	deviceTypeStr = "CD-ROM";
    else if (atapiGenConfig->deviceType == 7)
    	deviceTypeStr = "OPTICAL";
    else if (atapiGenConfig->deviceType == 1)
    	deviceTypeStr = "TAPE";
    else
    	deviceTypeStr = "UNKNOWN DEVICE TYPE";
	
    if (atapiGenConfig->cmdDrqType == 0)
    	cmdDrqTypeStr = "SLOW DRQ";
    else if (atapiGenConfig->cmdDrqType == 1)
    	cmdDrqTypeStr = "INTR DRQ";
    else if (atapiGenConfig->cmdDrqType == 2)
    	cmdDrqTypeStr = "FAST DRQ";
    else 
    	cmdDrqTypeStr = "UNKNOWN DRQ TYPE";
	
    if (atapiGenConfig->removable == 0)
    	removableStr = "NOT REMOVABLE";
    else if (atapiGenConfig->removable == 1)
    	removableStr = "REMOVABLE";
    
    if (atapiGenConfig->cmdPacketSize == 0)
    	cmdPacketSizeStr = "CMD PKT LEN=12";
    else if (atapiGenConfig->cmdPacketSize == 1)
    	cmdPacketSizeStr = "CMD PKT LEN=16";
    else 
    	cmdPacketSizeStr = "CMD PKT LEN=UNKNOWN";	
}

/*
 * This is a private version for ATAPI Identify command below. It has a short
 * delay time. 
 */
- (ide_return_t)atapiIdentifyDeviceWaitForDataReady
{
    int     delay = (100 * 1000);
    unsigned char status;

    delay -= 2;
    while (delay > 0) 
    {
	status = inb(_ideRegsAddrs.altStatus);
	if ((!(status & BUSY)) && (status & DREQUEST))
	    return (IDER_SUCCESS);
	if (delay % 1000) 	
        {
	    IODelay(2);
            delay -= 2;
	} 
        else 
        {
	    IOSleep(1);
	    delay -= 1000;
	}
    }
    
    return IDER_DEV_NOT_READY;
}

/*
 * There are lots of ATAPI CD-ROMs that shadow the task file register and
 * hence show up as two devices. They also might issue a valid interrupt when
 * ATAPI Identify Device command is sent so we need to really make sure
 * whether the device has data for us. 
 */
-(atapi_return_t)_atapiIdentifyDevice:(struct vm_map *)client
			    	addr:(caddr_t)xferAddr
{
    unsigned int cmd = ATAPI_IDENTIFY_DRIVE;
    unsigned char dh = ADDRESS_MODE_LBA;	/* ALWAYS */
    unsigned char status;
    atapi_return_t rtn;
    
    unsigned int oldTimeout;

    rtn = [self atapiWaitForNotBusy];
    if (rtn != IDER_SUCCESS) 
    {
	return (rtn);
    }
    
    [self setTransferRate: _driveNum UseDMA:NO];

    dh |= _driveNum ? SEL_DRIVE1 : SEL_DRIVE0;
    outb(_ideRegsAddrs.driveSelect, dh);
    
    bzero(xferAddr, IDE_SECTOR_SIZE);
    
    [self enableInterrupts];
    
    outb(_ideRegsAddrs.command, ATAPI_IDENTIFY_DRIVE);

    oldTimeout = [self interruptTimeOut];
    [self setInterruptTimeOut:4000];		// four seconds
    
    rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];
        
    if (rtn != IDER_SUCCESS) {
	[self getIdeRegisters:NULL Print:"Atapi Identify"];
	[self setInterruptTimeOut:oldTimeout];
	return rtn;
    }

    /*
     * Let's see if it really has data for us. 
     */
    rtn = [self atapiIdentifyDeviceWaitForDataReady];
    if (rtn != IDER_SUCCESS) 
    {
	[self setInterruptTimeOut:oldTimeout];
	return (rtn);
    }

    /*
     * FIXME: We need to do some sanity check on this data to be really sure
     * that there is an ATAPI device out here. 
     */
    [self xferData:xferAddr read:YES client:client length:IDE_SECTOR_SIZE];
    
    [self setInterruptTimeOut:oldTimeout];
    
    return IDER_SUCCESS;
}

/*
 * Some CD-ROMS (like SONY CDU-55D) fail the first request so we reset the
 * device and retry. 
 */
#define ATAPI_IDENTIFY_DEVICE_RETRIES		5

-(atapi_return_t)atapiIdentifyDevice:(struct vm_map *)client
			    	addr:(caddr_t)xferAddr
				unit:(unsigned char)unit
{
    int i;
    atapi_return_t ret;
    
    for (i = 0; i < ATAPI_IDENTIFY_DEVICE_RETRIES; i++)	
    {
	ret = [self _atapiIdentifyDevice:client addr:xferAddr];
	if (ret == IDER_SUCCESS)
	    break;
	    
	/* Reset hardware and try again */
	[self atapiSoftReset:unit];
	IOSleep(500);
    }
    
    if (i == ATAPI_IDENTIFY_DEVICE_RETRIES)	{
	IOLog("%s: FATAL: Device %d: ATAPI Identify Device.\n", 
		[self name], unit);
    }

    return ret;
}

-(void)atapiInitParameters:(ideIdentifyInfo_t *)infoPtr 
		Device:(unsigned char)unit
{
    atapiGenConfig_t *atapiGenConfig = 
    	(atapiGenConfig_t *) &infoPtr->genConfig;
    
    if (atapiGenConfig->cmdPacketSize == 0x01)
	_atapiCmdLen[unit] = 16;
    else
    	_atapiCmdLen[unit] = 12;

    /*
     * The command len is 12. However some devices think it is 16 which is
     * actually reserved for SAM compatibility. See page 58, SFF 8020, rev
     * 1.2. This is a workaround.
     */
    if (_atapiCmdLen[unit] != 12)	
    {
	IOLog("%s: ATAPI: command len changed to 12 from %d.\n", 
		[self name], _atapiCmdLen[unit]);
	_atapiCmdLen[unit] = 12;
    }

    _atapiCmdDrqType[unit] = atapiGenConfig->cmdDrqType;

    if (infoPtr->capabilities & IDE_CAP_DMA_SUPPORTED) 
    {
        if ( [self allocDmaMemory] == IDER_SUCCESS )
        {
            _dmaSupported[unit] = YES;
        }
    }

    [self getTransferModes:infoPtr Unit:unit];

    /*
     * Set the Disk/CDROM transfer mode (Set Features SC=3) 
     */
    [self setTransferMode: unit]; 
}

/*
 * Identify ATAPI device must have been executed first before callling this
 * method. 
 */
-(unsigned char)atapiCommandPacketSize:(unsigned char)unit
{
    if (unit < MAX_IDE_DRIVES)
	return _atapiCmdLen[unit];
    else
    	return 0;
}


/*
 * The amount of time in milliseconds it takes for an ATAPI device to post
 * its signature after a reset. Some CD-ROMs take a long time to respond. Do
 * not decrease it without testing with various makes of ATAPI devices. 
 */
#define ATAPI_RESET_DELAY		2500

-(atapi_return_t)atapiSoftReset:(unsigned char)unit
{
    int  delay;
    unsigned char status;
    atapi_return_t rtn;
    unsigned char dh = ADDRESS_MODE_LBA;	/* ALWAYS */

    _driveReset[unit] = YES;

    dh |= unit ? SEL_DRIVE1 : SEL_DRIVE0;

    outb(_ideRegsAddrs.driveSelect, dh);
    outb(_ideRegsAddrs.command, ATAPI_SOFT_RESET);

    delay = ATAPI_RESET_DELAY;
    
    IOSleep(50);			/* Enough time to assert busy */
    
    rtn = IDER_ERROR;
    
    while (delay > 0) 
    {
	status = inb(_ideRegsAddrs.status);
	if (!(status & BUSY)) 
        {
	    rtn = IDER_SUCCESS;
            break;
	}
	
	IOSleep(10);
	delay -= 10;
    }

    return rtn;
}


#define MAX_DRQ_WAIT 		(1000 * 2)		// milliseconds

-(atapi_return_t)atapiPacketCommand
{
    int i;
    atapi_return_t rtn;
    unsigned char interruptReason;
    unsigned char status;
	
    outb(_ideRegsAddrs.command, ATAPI_PACKET);

    /*
     * Some devices might have interrupted so we should take care of that
     * first.
     */
    if (_atapiCmdDrqType[_driveNum] == ATAPI_CMD_DRQ_INT) 
    {
	rtn = [self ideWaitForInterrupt:ATAPI_PACKET ideStatus:&status];
    }
    
    /*
     * Wait till we get okay from the device. 
     */
    for (i = 0; i < MAX_DRQ_WAIT; i++)	
    {
	interruptReason = inb(_ideRegsAddrs.interruptReason);
	if ((interruptReason & CMD_OR_DATA) && !(interruptReason & IO_DIRECTION))
	    break;
	IOSleep(1);
    }

    if (i == MAX_DRQ_WAIT)	
    {
	IOLog("%s: ATAPI Device %d:  Invalid Interrupt Reason: %x.\n", 
		[self name], _driveNum, interruptReason);
	return IDER_ERROR;
    }

    /*
     * Now DRQ should be set. 
     */
    status = inb(_ideRegsAddrs.status);
    if (status & DREQUEST)	
    {
	return IDER_SUCCESS;
    }
    
    IOLog("%s: ATAPI Device %d: No Data Request: %x.\n", 
    		[self name], _driveNum, status);
	
    return IDER_ERROR;
}

/*
 * Note that the command is sent out as words in little endian format. 
 */
- (void) sendAtapiCommand:(unsigned char *)atapiCmd
	cmdLen:(unsigned char)len
{
    int i;
    
    for (i = 0; i < len/2; i++)	
    {
    	outw(_ideRegsAddrs.data, (atapiCmd[2*i] << 8) | atapiCmd[2*i+1]);
    }
}

/*
 * Prints command and result in case of error. 
 */
- (void)dumpStatus:(atapiIoReq_t *)atapiIoReq
{
    int i;
    
    IOLog("%s: Failed command: drive: %d lun: %d len: %d read: %d\n", 
	[self name], 
	atapiIoReq->drive, atapiIoReq->lun, atapiIoReq->cmdLen, 
	atapiIoReq->read);
    IOLog("%s: Failed command: ", [self name]); 
    for (i = 0; i < atapiIoReq->cmdLen; i++)
	IOLog("%x ", atapiIoReq->atapiCmd[i]);
    IOLog("\n");
    
    [self getIdeRegisters:NULL Print:"dumpStatus"];
}


#define	C10OP_MODESELECT	0x55	/* OPT: set device parameters */
#define	C10OP_MODESENSE		0x5a	/* OPT: get device parameters */

#define MAX_SENDPACKET_RETRIES		3

#define MAX_BUSY_WAIT 			(1000*100)

- (sc_status_t) atapiExecuteCmd:(atapiIoReq_t *)atapiIoReq 
		    buffer : (void *)buffer 
		    client : (struct vm_map *)client
{
    int 		i;
    atapi_return_t 	rtn;
    unsigned int 	offset;
    unsigned char 	dh = ADDRESS_MODE_LBA;	/* ALWAYS */
    unsigned char       xferType;
    BOOL		dmaAllowed;
    
    /*
     * Reject command if there are no ATAPI devices detected. 
     */
     if ((atapiIoReq->drive >= MAX_IDE_DRIVES) || (atapiIoReq->lun != 0))  
     {
	return SR_IOST_SELTO;
     }
     
     if ([self isAtapiDevice:atapiIoReq->drive] == NO)	
     {
	return SR_IOST_SELTO;
     }
         
    [self clearInterrupts];
    
    _driveNum = atapiIoReq->drive;

    dmaAllowed = [self atapiDmaAllowed: buffer];

    if ( atapiIoReq->maxTransfer != 0 )
    {
        [self setTransferRate: _driveNum UseDMA:dmaAllowed];
    }

    if ( _driveReset[_driveNum] == YES )
    {
        [self setTransferMode: _driveNum];
        _driveReset[_driveNum] = NO;
    }

    /* Now execute the SCSI command. */

    for (i = 0; i < MAX_SENDPACKET_RETRIES; i++)	
    {
	rtn = [self atapiWaitReadyForPacket];
	if (rtn != IDER_SUCCESS) 
        {
	    IOLog("%s: ATAPI Device %d: Not Ready For Packet command.\n", 
	    	[self name], _driveNum);
	    [self atapiSoftReset:_driveNum];
	    continue;
	}
	
	/*
	 * Select the drive first. 
	 */
	dh |= _driveNum ? SEL_DRIVE1 : SEL_DRIVE0;
	outb(_ideRegsAddrs.driveSelect, dh);

	/*
	 * Send our preferred data transfer size (2048 bytes). 
	 */
	outb(_ideRegsAddrs.byteCountLow, 0x00);
	outb(_ideRegsAddrs.byteCountHigh, 0x08);

	/*
	 * Set data transfer mode PIO = 0x00 / DMA = 0x01 
	 */
        xferType = (dmaAllowed == YES) ? 0x01 : 0x00;
	outb(_ideRegsAddrs.features, xferType);
	
	/*
	 * First tell the ATAPI device that we are going to send it a packet
	 * command. If this command fails the ATAPI device needs to be reset. 
	 */
	if ([self atapiPacketCommand] == IDER_SUCCESS)	
        {
	    break;
	}
	
	IOLog("%s: ATAPI Device %d: Packet command failed. Retrying...\n",
		 [self name], _driveNum);
	[self atapiSoftReset:_driveNum];
    }

    /*
     * Are we hosed? 
     */
    if (i == MAX_SENDPACKET_RETRIES)	
    {
	atapiIoReq->scsiStatus = STAT_CHECK;
	IOLog("%s: ATAPI Device %d: FATAL: Packet command.\n", 
		[self name], _driveNum);
	return SR_IOST_CMDREJ;    
    }
    
    /*
     * Send the actual command to the device. 
     */
    [self sendAtapiCommand:atapiIoReq->atapiCmd cmdLen:atapiIoReq->cmdLen];

    atapiIoReq->bytesTransferred = 0;
    offset = 0;
    
    [self enableInterrupts];

    if ( dmaAllowed == YES )
    {
        rtn = [self atapiXferDMA:(atapiIoReq_t *)atapiIoReq  buffer: (void *)buffer client: (struct vm_map *)client];
    }
    else
    {
        rtn =[self atapiXferPIO:(atapiIoReq_t *)atapiIoReq  buffer: (void *)buffer client: (struct vm_map *)client];
    }

#if 0
        kprintf("Disk(ATAPI): Cmd = %02x Length = %08x Addr=%08x:%08x Status = %d\n\r", 
               atapiIoReq->atapiCmd[0], (int)atapiIoReq->maxTransfer, (int)buffer, (int)client, 
               rtn);
#endif   

    return rtn;
}

- (sc_status_t) atapiXferPIO:(atapiIoReq_t *)atapiIoReq 
		    buffer : (void *)buffer 
		    client : (struct vm_map *)client
{
    int 		i;
    atapi_return_t 	rtn;
    unsigned char 	status;
    unsigned int 	bytes;
    unsigned int 	offset = 0;
    unsigned char 	cmd = atapiIoReq->atapiCmd[0];

    for (;;)	
    {
	rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];

	if (rtn != IDER_SUCCESS) 
        {
	    IOLog("%s: FATAL: ATAPI Device: %d Command %x failed.\n", 
	    	[self name], _driveNum, atapiIoReq->atapiCmd[0]);
	    [self getIdeRegisters:NULL Print:"ATAPI Command"];
	    [self atapiSoftReset:_driveNum];
	    atapiIoReq->scsiStatus = STAT_CHECK;
	    return SR_IOST_CHKSNV;
	}

	for (i = 0; i < MAX_BUSY_WAIT; i++)	
        {
	    if (status & BUSY)	
            {
		IODelay(10);
	    } 
            else 
            {
	    	break;
	    }
	    status = inb(_ideRegsAddrs.status);	
	}
	
	/*
	 * If DRQ == 0 then host has terminated the command. 
	 */
	if (!(status & DREQUEST))	
        {
	    /*
	     * Check for command completion status. 
	     */
	    if (status & ERROR)	
            {		    
		atapiIoReq->scsiStatus = STAT_CHECK;
		return SR_IOST_CHKSNV;
	    } 
            else 
            {
		atapiIoReq->scsiStatus = STAT_GOOD;
		return SR_IOST_GOOD;
	    }
	}

	/*
	 * Command is not completed. We need to transfer data as requested by
	 * the device. 
	 */
	bytes =  inb(_ideRegsAddrs.byteCountHigh) << 8 | 
	    inb(_ideRegsAddrs.byteCountLow);

	/*
	 * If the device requests more data to be transferred than required
	 * by the command protocol, then we should transfer null data. 
	 */

	
	if (atapiIoReq->bytesTransferred + bytes > atapiIoReq->maxTransfer)
        {
	    IOLog("%s: ATAPI Device %d: "
		"ERROR: Transfer limit (%x bytes) exceeded\n", 
	    	[self name], _driveNum, atapiIoReq->maxTransfer);
		
	    IOLog("%s: Bytes already transfered: %x, next request: %x\n", 
	    	[self name], atapiIoReq->bytesTransferred, bytes);
	    
	    [self dumpStatus: atapiIoReq];

	    /*
	     * This thing is probably hosed but let's do the best we can. 
	     */
	    {
	    int diff = atapiIoReq->maxTransfer-atapiIoReq->bytesTransferred;
	    IOLog("%s: Transferring %x bytes instead of %x\n", 
	    	[self name], diff, bytes);
	    [self xferData:buffer+offset read:atapiIoReq->read 
		    client:client length:diff];
	    atapiIoReq->bytesTransferred += diff;
	    offset += diff;
	    [self atapiSoftReset:_driveNum];	// reset hardware as well
	    }
	    
	    atapiIoReq->scsiStatus = STAT_GOOD;
	    return SR_IOST_GOOD;	    
	}
	
	/*
	 * Now transfer data from device to memory. 
	 */
	[self xferData:buffer+offset read:atapiIoReq->read client:client 
		length:bytes];
	
	atapiIoReq->bytesTransferred += bytes;
	offset += bytes;
    }

    /*
     * Will never get here. 
     */
    atapiIoReq->scsiStatus = STAT_GOOD;
    return SR_IOST_GOOD;
}


- (sc_status_t) atapiXferDMA:(atapiIoReq_t *)atapiIoReq 
		    buffer : (void *)buffer 
		    client : (struct vm_map *)client
{
    int 		i;
    atapi_return_t 	rtn;
    unsigned char 	status;
    unsigned char 	cmd = atapiIoReq->atapiCmd[0];
    unsigned long	cfgReg;

    [self setupDMA:(vm_offset_t) buffer client:(vm_task_t) client length:(int)atapiIoReq->maxTransfer fRead:(BOOL)atapiIoReq->read]; 
    if ( _controllerType != kControllerTypeCmd646X )
    {
        IODBDMAContinue( _ideDMARegs );        
    }
    else
    {
        [[self deviceDescription] configReadLong:0x70 value: &cfgReg];
        cfgReg &= ~0x08;
        cfgReg |= 0x01 | ((atapiIoReq->read) ? 0x08 : 0x00);
        [[self deviceDescription] configWriteLong:0x70 value: cfgReg];
    }

    rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];

    if (rtn != IDER_SUCCESS) 
    {
	IOLog("%s: FATAL: ATAPI Device: %d Command %x failed.\n", [self name], _driveNum, atapiIoReq->atapiCmd[0]);
	[self getIdeRegisters:NULL Print:"ATAPI Command"];
	[self atapiSoftReset:_driveNum];
	atapiIoReq->scsiStatus = STAT_CHECK;
	return SR_IOST_CHKSNV;
    }

    for (i = 0; i < MAX_BUSY_WAIT; i++)	
    {
	if (status & BUSY)	
        {
            IODelay(10);
	} 
        else 
        {
	    break;
	}
        status    = inb(_ideRegsAddrs.status);
    }

    if ( _controllerType != kControllerTypeCmd646X )
    {
        atapiIoReq->bytesTransferred = [self stopDBDMA];
    }
    else
    {
        [[self deviceDescription] configReadLong: 0x70 value: &cfgReg];
        cfgReg &= ~0x01;
        [[self deviceDescription] configWriteLong:0x70 value: cfgReg];

        atapiIoReq->bytesTransferred = atapiIoReq->maxTransfer;
    }

    /*
     * The DMA has handled all the data movement. 
     * Must have final command status at this point. 
     */
     if ( status & (ERROR) )	
     {		    
         atapiIoReq->scsiStatus = STAT_CHECK;
         return SR_IOST_CHKSNV;
     } 
     else 
     {
	  atapiIoReq->scsiStatus = STAT_GOOD;
	  return SR_IOST_GOOD;
     }
}     
  
- (BOOL) atapiDmaAllowed: (void *)buffer
{
    if ( _dmaSupported[_driveNum] == NO ) 
    {
        return NO;
    }
    
    if ( (_controllerType == kControllerTypeCmd646X) && ((u_int)buffer & 1) )
    {
        return NO;
    }

    return YES;
}
        
      
@end