File:  [Apple Darwin 0.x] / drvEIDE / EIDE.drvproj / EIDE.lksproj / IdeCntCmds.m
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 17:41:00 2018 UTC (8 years, 1 month ago) by root
Branches: MAIN, Apple
CVS tags: HEAD, Darwin03
Darwin 0.3 EIDE device driver

/*
 * 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-1998 by Apple Computer, Inc., All rights reserved.
 * Copyright 1994-1997 NeXT Software, Inc., All rights reserved.
 *
 * IdeCntCmds.m - Implementation of Commands category for IDE Controller 
 * device. 
 *
 * It contains implementation of the ATA command set. 
 *
 *
 * HISTORY 
 *
 * 23-Feb-1998		Joe Liu at Apple
 *  startUpAttachedDevices method is called immediately after acquiring
 *  the lock.
 *
 * 1-Feb-1998		Joe Liu at Apple
 *	ideSetDriveFeature method changed to support all EIDE transfer modes.
 *	Added support for Bus Master DMA.
 *  ideExecuteCmd now sets _driveNum within the retry loop.
 *
 * 1-Aug-1995	 	Rakesh Dubey at NeXT 
 *	Reduced the timeout in IDE_IDENTIFY_DRIVE method. 
 *
 * 17-July-1994 	Rakesh Dubey at NeXT 
 *	Created. 
 */

//#define DEBUG

#import "IdeCnt.h"
#import "IdeCntInit.h"
#import "IdePIIX.h"
#import <kern/assert.h>
#import <driverkit/kernelDriver.h>
#import <driverkit/interruptMsg.h>
#import <mach/mach_interface.h>
#import <driverkit/IODevice.h>
#import <driverkit/align.h>
#import <machkit/NXLock.h>
#import <machdep/i386/io_inline.h>
#import "IdeDDM.h"

/*
 * All methods in this file implement one or more of the IDE commands. These
 * commands get invoked by IdeDisk objects. 
 */

@implementation IdeController(Commands)

/*
 * Returns a struct containing IDE registers values for a given sector
 * address and size. Depending upon whether the drive supports LBA or CHS we
 * fill in different values. The calling routine must make sure that these
 * values are not overwritten. The only thing that they need to fill in is
 * the drive number. 
 */
- (ideRegsVal_t)logToPhys:(unsigned)block numOfBlocks:(unsigned)nblk
{
    ideRegsVal_t rv;
    unsigned int cyl, nheads;
    unsigned int track, spt;

    if (_drives[_driveNum].addressMode == ADDRESS_MODE_CHS) {
		nheads = _drives[_driveNum].ideInfo.heads;
		spt = _drives[_driveNum].ideInfo.sectors_per_trk;
	
		track = (block / spt);
		cyl = track / nheads;
		rv.sectNum = block % spt + 1;
	
		rv.drHead = track % nheads;
		rv.cylLow = (cyl & 0xff);
		rv.cylHigh = ((cyl & 0xff00) >> 8);
    } else {
        rv.sectNum = block & 0x0ff;
		rv.cylLow = (block >> 8) & 0xff;
		rv.cylHigh = (block >> 16) & 0xff;
		rv.drHead = (block >> 24) & 0x0f;
    }
    
    rv.sectCnt = (nblk == MAX_BLOCKS_PER_XFER) ? 0 : nblk;

    rv.drHead |= _drives[_driveNum].addressMode;
    rv.drHead |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
	    
    return rv;
}

/*
 * Commands to the IDE controller. 
 */


- (ide_return_t)ideDiagnose:(unsigned *)diagError
{
    ide_return_t rtn;
    unsigned char status;
    
    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS)
    	return IDER_CMD_ERROR;
	
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_DIAGNOSE);
    
    rtn = [self ideWaitForInterrupt:IDE_DIAGNOSE ideStatus:&status];
    if (rtn != IDER_SUCCESS)	{
	[self getIdeRegisters:NULL Print:"Diagnose"];
	return IDER_CMD_ERROR;
    }

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS)
    	return IDER_CMD_ERROR;
    
    *diagError = inb(_ideRegsAddrs.error);
    
    if (*diagError == 0x01)	{
        /* FIXME: do we need to soft reset ATAPI devices? */
	return IDER_SUCCESS;
    }

    /*
     * At least one of the drives is bad. 
     */
    if (*diagError & 0x080) {
	IOLog("%s: Drive 1 has failed diagnostics.\n", [self name]);
	_drives[1].ideInfo.type = 0;

	if ((*diagError & 0x07f) != 1) {
	    IOLog("%s: Drive 0 has failed diagnostics, error %d\n",
		    [self name], (*diagError & 0x07f));
	    return IDER_CMD_ERROR;
	}
    } else {
	_drives[0].ideInfo.type = 0;
	IOLog("%s: Drive 0 has failed diagnostics, error %d\n",
		[self name], (*diagError & 0x07f));
    }
    
    return IDER_CMD_ERROR;
}

- (ide_return_t)ideSetParams:(unsigned)sectCnt numHeads:(unsigned)nHeads
			ForDrive:(unsigned)drive
{
    unsigned char dh = _drives[drive].addressMode;
    ide_return_t rtn;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS)
    	return rtn;
	
    dh |= ((drive ? SEL_DRIVE1 : SEL_DRIVE0) | ((nHeads - 1) & 0x0f));
    outb(_ideRegsAddrs.drHead, dh);
    outb(_ideRegsAddrs.sectCnt, (sectCnt & 0xff));
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_SET_PARAMS);
    
    rtn = [self ideWaitForInterrupt:IDE_SET_PARAMS ideStatus:NULL];
    if (rtn != IDER_SUCCESS) {
	[self getIdeRegisters:NULL Print:"SetParams"];
	return rtn;
    }
    
    return rtn;
}

- (ide_return_t)ideSetDriveFeature:(unsigned char)feature
	value:(unsigned char)val
	transferType:(ideTransferType_t)type
{
    unsigned char dh = _drives[_driveNum].addressMode;
    unsigned char status;
    ide_return_t rtn;
//	unsigned char tmp;

#ifdef DEBUG
    IOLog("Setting drive feature %x to %x\n", feature, val);
#endif DEBUG

    /*
     * We are not going to do anything if EIDE support is disabled.
	 *
	 * Currently, this is useful only for setting the transfer mode.
     */
    if ((_EIDESupport == NO) || (feature != FEATURE_SET_TRANSFER_MODE) ||
		(val == ATA_MODE_NONE)) 
    	return IDER_SUCCESS;
	
	val = ata_mode_to_num(val);
//	IOLog("Mode num: %d\n", val);
	
    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS)
    	return rtn;
	
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);
	
	val &= 0x07;
	switch (type) {
		case IDE_TRANSFER_PIO:
			val |= (1 << 3);
			break;
		case IDE_TRANSFER_SW_DMA:
			val |= (1 << 4);
			break;
		case IDE_TRANSFER_MW_DMA:
			val |= (1 << 5);
			break;
		case IDE_TRANSFER_ULTRA_DMA:
			val |= (1 << 6);
			break;
		default:
			val = 0;
	}
	outb(_ideRegsAddrs.sectCnt, val);
    outb(_ideRegsAddrs.features, feature);
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_SET_FEATURES);
    
    rtn = [self ideWaitForInterrupt:IDE_SET_FEATURES ideStatus:&status];
    if (rtn != IDER_SUCCESS) {
		[self getIdeRegisters:NULL Print:"SetFeatures"];
		return rtn;
    }
    
    if (status & ERROR)
    	return IDER_ERROR;
    
    return rtn;
}

- (ide_return_t)ideRestore:(ideRegsVal_t *)ideRegs
{
    ide_return_t rtn;
    unsigned char status;
    unsigned char dh = _drives[_driveNum].addressMode;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return rtn;
    }
    
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_RESTORE);

    rtn = [self ideWaitForInterrupt:IDE_RESTORE ideStatus: &status];
    if (rtn != IDER_SUCCESS) {
	[self getIdeRegisters:NULL Print:"Restore"];
	return rtn;
    }
    
    rtn = [self waitForDeviceReady];
    if (rtn == IDER_SUCCESS) {
	if (status & ERROR) {
	    [self getIdeRegisters:ideRegs Print:"Restore"];
	    rtn = IDER_CMD_ERROR;
	}
    }

    return (rtn);
}

- (ide_return_t)ideReadGetInfoCommon:(ideRegsVal_t *)ideRegs
			    client:(struct vm_map *)client
			    addr:(caddr_t)xferAddr
			    command:(unsigned int)cmd
{
    ide_return_t rtn;
    unsigned int sec_cnt = ideRegs->sectCnt;
    int i;
    unsigned char *taddr;
    unsigned char status;
    unsigned char dh = _drives[_driveNum].addressMode;

    if (sec_cnt == 0)
	sec_cnt = MAX_BLOCKS_PER_XFER;
	
    taddr = xferAddr;

    /*
     * We have to define this for the IDE_IDENTIFY_DRIVE command. 
     */
    if (cmd == IDE_IDENTIFY_DRIVE)	{
	ideRegs->sectCnt = 1;
	sec_cnt = 1;
    }

    /*
     * Select the drive first. This routine is invoked by the initialization
     * code as well (-resetAndInit) so it is necessary to do this here. 
     */
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);

    
    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
        if (_ide_debug)	{
	    IOLog("ideReadGetInfoCommon: waitForDeviceReady\n");
	    [self getIdeRegisters:ideRegs Print:NULL];
	}
	return (rtn);
    }
    
    if (cmd == IDE_READ) {
	outb(_ideRegsAddrs.drHead, ideRegs->drHead);
	outb(_ideRegsAddrs.sectNum, ideRegs->sectNum);
	outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
	outb(_ideRegsAddrs.cylLow, ideRegs->cylLow);
	outb(_ideRegsAddrs.cylHigh, ideRegs->cylHigh);
    } else {
        /* probably unnecessary */
	outb(_ideRegsAddrs.drHead, dh);
	outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
    }
        
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, cmd);
        
    for (i = 0; i < sec_cnt; i++)	{

	rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];
	if (rtn != IDER_SUCCESS) {
	    if (_ide_debug)	{
		IOLog("ideReadGetInfoCommon: ideWaitForInterrupt\n");
		[self getIdeRegisters:ideRegs Print:NULL];
	    }
	    return rtn;
	} else {
	    /*
	    if (status & ERROR_CORRECTED) {
		IOLog("%s: Corrected error during read.\n", [self name]);
	    }
	    */
	}
    
   /*
	* Same as waitForDataReady but with a quick timeout. 
	*/
	rtn = [self ataIdeReadGetInfoCommonWaitForDataReady];
	if (rtn != IDER_SUCCESS) {
	    if (_ide_debug)	{
		IOLog("ideReadGetInfoCommon: "
			"ataIdeReadGetInfoCommonWaitForDataReady\n");
		[self getIdeRegisters:ideRegs Print:NULL];
	    }
	    return (rtn);
	}
	
	[self xferData:taddr read:YES client:client length:IDE_SECTOR_SIZE];
	taddr += IDE_SECTOR_SIZE;
    }

    return rtn;
}

- (ide_return_t)ideWrite:(ideRegsVal_t *)ideRegs
		    client:(struct vm_map *)client
		    addr:(caddr_t)xferAddr
{
    ide_return_t rtn;
    unsigned int sec_cnt = ideRegs->sectCnt;
    int i;
    unsigned char *taddr;
    unsigned char status;

    if (sec_cnt == 0)
	sec_cnt = MAX_BLOCKS_PER_XFER;
    
    taddr = xferAddr;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    outb(_ideRegsAddrs.drHead, ideRegs->drHead);
    outb(_ideRegsAddrs.sectNum, ideRegs->sectNum);
    outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
    outb(_ideRegsAddrs.cylLow, ideRegs->cylLow);
    outb(_ideRegsAddrs.cylHigh, ideRegs->cylHigh);
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_WRITE);

    for (i = 0; i < sec_cnt; i++)	{

	rtn = [self waitForDataReady];
	if (rtn != IDER_SUCCESS) {
	    return (rtn);
	}

	[self xferData:taddr read:NO client:client length:IDE_SECTOR_SIZE];
	taddr += IDE_SECTOR_SIZE;

	rtn = [self ideWaitForInterrupt:IDE_WRITE ideStatus:&status];
    
	if (rtn != IDER_SUCCESS) {
	    [self getIdeRegisters:ideRegs Print:"Write"];
	    return (rtn);
	} else {
	    /*
	    if (status & ERROR_CORRECTED) {
		IOLog("%s: Corrected error during write.\n", [self name]);
	    }
	    */
	}
    }

    return (rtn);
}

- (ide_return_t)ideReadVerifySeekCommon:(ideRegsVal_t *)ideRegs
			    command:(unsigned int)cmd
{
    ide_return_t rtn;
    unsigned char status;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    outb(_ideRegsAddrs.drHead, ideRegs->drHead);
    outb(_ideRegsAddrs.sectNum, ideRegs->sectNum);
    if (cmd == IDE_READ_VERIFY)
	outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
    outb(_ideRegsAddrs.cylLow, ideRegs->cylLow);
    outb(_ideRegsAddrs.cylHigh, ideRegs->cylHigh);

    [self enableInterrupts];
    outb(_ideRegsAddrs.command, cmd);
    
    rtn = [self ideWaitForInterrupt:cmd ideStatus: &status];
    if (rtn != IDER_SUCCESS) {
	[self getIdeRegisters:NULL Print:"ReadVerify/Seek"];
	return (rtn);
    }
    
    if (status & (ERROR | WRITE_FAULT)) {
	rtn = IDER_CMD_ERROR;
    } else {
	if ((cmd == IDE_SEEK) && (!(status & SEEK_COMPLETE)))
	    rtn = IDER_CMD_ERROR;
    }

    return (rtn);
}

- (ide_return_t)ideSetMultiSectorMode:(ideRegsVal_t *)ideRegs
			    numSectors:(unsigned char)nSectors
{
    ide_return_t rtn;
    unsigned char status;
    unsigned char dh = _drives[_driveNum].addressMode;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);
    outb(_ideRegsAddrs.sectCnt, nSectors);
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_SET_MULTIPLE);

    rtn = [self ideWaitForInterrupt:IDE_SET_MULTIPLE ideStatus:&status];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    rtn = [self waitForDeviceReady];
    if (rtn == IDER_SUCCESS) {
	if (status & ERROR) {
	    rtn = IDER_CMD_ERROR;
	    [self getIdeRegisters:ideRegs Print:NULL];
	} else
	    rtn = IDER_SUCCESS;
    } 

    return (rtn);
}

/*
 * Note: Never read the status register at the end of data transfer, you may
 * clobber the next interrupt from the drive. If it is necessary to get
 * status use the alternate status register. 
 */
- (ide_return_t)ideReadMultiple:(ideRegsVal_t *)ideRegs
	client:(struct vm_map *)client
	addr:(caddr_t)xferAddr
{
    ide_return_t rtn;
    unsigned char status;
    unsigned sec_cnt = ideRegs->sectCnt;
    unsigned int nSectors;
    unsigned char *taddr;
    unsigned int length;

    if (sec_cnt == 0)
	sec_cnt = MAX_BLOCKS_PER_XFER;
	
    taddr = xferAddr;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    outb(_ideRegsAddrs.drHead, ideRegs->drHead);
    outb(_ideRegsAddrs.sectNum, ideRegs->sectNum);
    outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
    outb(_ideRegsAddrs.cylLow, ideRegs->cylLow);
    outb(_ideRegsAddrs.cylHigh, ideRegs->cylHigh);

    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_READ_MULTIPLE);

    ddm_ide_cmd("ideReadMultiple: sec_cnt %d sectors\n", sec_cnt, 2,3,4,5);
    
    while (sec_cnt > 0) {

	ddm_ide_cmd("ideReadMultiple: waiting for interrupt\n",1,2,3,4,5);
	rtn = [self ideWaitForInterrupt:IDE_READ_MULTIPLE
			ideStatus: &status];
	ddm_ide_cmd("ideReadMultiple: received interrupt\n",1,2,3,4,5);

	if (rtn != IDER_SUCCESS) {
	    [self getIdeRegisters:ideRegs Print:"Read Multiple"];
	    return (rtn);
	}

#if 1	
	rtn = [self waitForDataReady];
	if (rtn != IDER_SUCCESS) {
	    return (rtn);
	}
#endif 1

	if (status & (ERROR | WRITE_FAULT)) {
	    [self getIdeRegisters:ideRegs Print:"Read Multiple"];
	    return IDER_CMD_ERROR;
	}

	/*
	 * Any drive formatted with 63 sector/track (which most over 400 MB
	 * are) reporting this status (ERROR_CORRECTED) will cause an
	 * fallacious error (possibly uncorrectable) due to a long-standing
	 * bug in DOS. This status bit should be made vendor specific, like
	 * IDX. It has outlived its usefulness. 
	 * -- [email protected] 
	 */
	 
	/*
	if (status & ERROR_CORRECTED) {
	    IOLog("%s: Corrected error during read.\n", [self name]);
	}
	*/

	/*
	 * All is well. Read in the data. 
	 */
	if (sec_cnt > _drives[_driveNum].multiSector)
	    nSectors = _drives[_driveNum].multiSector;
	else
	    nSectors = sec_cnt;
	    
	sec_cnt -= nSectors;

	ddm_ide_cmd("ideReadMultiple: starting data transfer\n",1,2,3,4,5);

	while (nSectors) {
	    if (nSectors > PAGE_SIZE / IDE_SECTOR_SIZE) {
		length = PAGE_SIZE;
		nSectors -= (PAGE_SIZE / IDE_SECTOR_SIZE);
	    } else {
		length = nSectors * IDE_SECTOR_SIZE;
		nSectors = 0;
	    }

	    [self xferData:taddr read:YES client:client length:length];
	    taddr += length;
	}

	ddm_ide_cmd("ideReadMultiple: data transfer done\n",1,2,3,4,5);
    }

    ddm_ide_cmd("ideReadMultiple: completed\n",1,2,3,4,5);

    return (rtn);
}

- (ide_return_t)ideWriteMultiple:(ideRegsVal_t *)ideRegs
	client:(struct vm_map *)client 
	addr:(caddr_t)xferAddr
{
    ide_return_t rtn;
    unsigned char status;
    unsigned sec_cnt = ideRegs->sectCnt;
    unsigned int nSectors;
    unsigned char *taddr;
    unsigned int length;

    if (sec_cnt == 0)
	sec_cnt = MAX_BLOCKS_PER_XFER;
	
    taddr = xferAddr;

    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
	return (rtn);
    }
    
    outb(_ideRegsAddrs.drHead, ideRegs->drHead);
    outb(_ideRegsAddrs.sectNum, ideRegs->sectNum);
    outb(_ideRegsAddrs.sectCnt, ideRegs->sectCnt);
    outb(_ideRegsAddrs.cylLow, ideRegs->cylLow);
    outb(_ideRegsAddrs.cylHigh, ideRegs->cylHigh);
    
    [self enableInterrupts];
    outb(_ideRegsAddrs.command, IDE_WRITE_MULTIPLE);

    ddm_ide_cmd("ideWriteMultiple: sec_cnt %d sectors\n", sec_cnt, 2,3,4,5);

    while (sec_cnt > 0) {
    
	rtn = [self waitForDataReady];
	if (rtn != IDER_SUCCESS) {
	    return (rtn);
	}
	
	if (sec_cnt > _drives[_driveNum].multiSector)
	    nSectors = _drives[_driveNum].multiSector;
	else
	    nSectors = sec_cnt;
	    
	sec_cnt -= nSectors;

	ddm_ide_cmd("ideWriteMultiple: starting data transfer\n",1,2,3,4,5);

	while (nSectors) {
	    if (nSectors > PAGE_SIZE / IDE_SECTOR_SIZE) {
		length = PAGE_SIZE;
		nSectors -= (PAGE_SIZE / IDE_SECTOR_SIZE);
	    } else {
		length = nSectors * IDE_SECTOR_SIZE;
		nSectors = 0;
	    }

	    [self xferData:taddr read:NO client:client length:length];
	    taddr += length;
	}
	ddm_ide_cmd("ideWriteMultiple: data transfer done\n",1,2,3,4,5);

	ddm_ide_cmd("ideWriteMultiple: waiting for interrupt\n",1,2,3,4,5);
	rtn = [self ideWaitForInterrupt:IDE_WRITE_MULTIPLE 
				ideStatus:&status];
	ddm_ide_cmd("ideWriteMultiple: received interrupt\n",1,2,3,4,5);

	if (rtn != IDER_SUCCESS) {
	    [self getIdeRegisters:ideRegs Print:"Write Multiple"];
	    return (rtn);
	}
	
	if (status & (ERROR | WRITE_FAULT)) {
	    [self getIdeRegisters:ideRegs Print:"Write Multiple"];
	    return IDER_CMD_ERROR;
	}
	
	/*
	if (status & ERROR_CORRECTED) {
	    IOLog("%s: Corrected error during write.\n", [self name]);
	}
	*/
	
    }

    ddm_ide_cmd("ideWriteMultiple: completed\n",1,2,3,4,5);

    return (rtn);
}

/*
 * Method: performDMATestOnDrive:(unsigned char)drive
 *
 * Purpose:
 * Read a few sectors and make sure DMA really "seems" to works.
 *
 * NOTE: _driveNum must be set prior to calling this method.
 */
- (ide_return_t)performDMATest
{
    ideIoReq_t	ideIoReq;
    ide_return_t status;
    vm_offset_t tempDmaBuf;
	vm_offset_t alignBuf;
	unsigned int currentTimeout;
    unsigned char 	dh;
	
    /*
     * The hardware claims to supports DMA. Verify by reading a
     * few sectors. Allocate memory for dummy buffer.
     */
	tempDmaBuf = (vm_offset_t)IOMalloc(PAGE_SIZE);
    if (tempDmaBuf == NULL)	{
		IOLog("%s: memory allocation failed\n", [self name]);
		return IDER_REJECT;
    }

	/*
	 * Advance the pointer and make the buffer 4 byte aligned.
	 */
	alignBuf = (tempDmaBuf + 3) & ~3;

	bzero((unsigned char *)&ideIoReq, sizeof(ideIoReq_t));
    ideIoReq.cmd = IDE_READ_DMA;
    ideIoReq.block = 0;
    ideIoReq.blkcnt = (PAGE_SIZE - 4)/IDE_SECTOR_SIZE;
    ideIoReq.addr = (caddr_t)alignBuf;
    ideIoReq.timeout = 5000;
    ideIoReq.map = (struct vm_map *)IOVmTaskSelf();
	
	/*
	 * Select the drive first.
	 */
	if ([self waitForDeviceIdle] != IDER_SUCCESS) {
		IOLog("%s: Drive %d DMA test FAILED\n", [self name], _driveNum);
		return IDER_TIMEOUT;
	}
    dh = _drives[_driveNum].addressMode;
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);
	
	/*
	 * Wait 400ns before reading the status register and make sure the
	 * currently selected drive is ready to accept a command.
	 */
	IODelay(1);
	if ([self waitForDeviceIdle] != IDER_SUCCESS) {
		IOLog("%s: Drive %d DMA test FAILED\n", [self name], _driveNum);
		return IDER_TIMEOUT;
	}	
	
	/*
	 * Clear any unwanted interrupts which may have accumulated.
	 */
	[self enableInterrupts];
	IOSleep(100);
	
	/*
	 * Set a smaller (3 sec) timeout for this test.
	 */
	currentTimeout = [self interruptTimeOut];
	[self setInterruptTimeOut:3000];
	[self clearInterrupts];
	
	/*
	 * Perform test.
	 */
	if (([self performDMA:(ideIoReq_t *)&ideIoReq]) == IDER_SUCCESS) {
		status = IDER_SUCCESS;
//		IOLog("%s: Drive %d: DMA test PASSED\n", [self name], _driveNum);
	}
	else {
		status = IDER_REJECT;
    	IOLog("%s: Drive %d: DMA test FAILED\n", [self name], _driveNum);
	}
	
	/*
	 * Revert the original timeout value and free allocated memory.
	 */
	[self setInterruptTimeOut:currentTimeout];
    IOFree((void *)tempDmaBuf, PAGE_SIZE);
	return status;
}

/*
 * All I/O to to controller object is done through this method. We first
 * acquire a lock before executing any IDE commands since the IDE controller
 * can do only one thing at a time (both drivers can not be active
 * simultaneously except for reset and disgnostics). 
 *
 * If it necessary to call any of the IDE command methods (which are invoked by
 * the switch() below, like ideReadGetInfoCommon:client:addr:command) it is
 * necessary to acquire the lock. The IDE command methods should not be
 * invoked directly by the Disk object. 
 *
 * This method will in turn call one of several methods which deal with
 * hardware. 
 */

static unsigned char unaligned_warnings;
#define UNALIGNED_WARNINGS_MAX	20

#define MAX_COMMAND_RETRY 	3

- (IOReturn) ideExecuteCmd:(ideIoReq_t *)ideIoReq ToDrive:(unsigned char)drive
{
    int     		retry;
    ideRegsVal_t 	irv;
    unsigned 		block, cnt;
    unsigned 		error;
    unsigned int 	maxSectors;
    unsigned char 	dh;

    ddm_ide_cmd("ideExecuteCmd: executing %x\n", ideIoReq->cmd,2,3,4,5);

    /*
     * If the controller wishes to put the drive to sleep, it sets this flag
     * to YES and tries to acquire the lock. This method should not try to
     * get the lock (and execute any more commands) if the flag is set. This
     * is needed in order to enter sleep mode as soon as the current command
     * is finished. If we do not do this there is contention for lock between
     * the controller (which wants to put the drive to sleep) and this method
     * (which wants to service more requests). Note that this does not do
     * away with the need for a lock, it only gives the controller a little
     * head-start. 
     */
    
    while (_driveSleepRequest) {
		IOSleep(100);
    }
     
    ddm_ide_lock("ideExecuteCmd: acquiring lock\n",1,2,3,4,5);
    [self ideCntrlrLock];
    ddm_ide_lock("ideExecuteCmd: acquired lock\n",1,2,3,4,5);

   /*
    * Check if we need to do a media access to get the drive respinning
    * after a suspend operation. 
    */
    if ([self drivePowerState] != IDE_PM_ACTIVE) {
		[self startUpAttachedDevices];
    }

    _driveNum = drive;		/* used by IDE command methods. */
	
    for  (retry = 0; retry < MAX_COMMAND_RETRY; retry++) {
    
    /*
     * Select the drive first. We don't know the head number at this time so
     * this register will be rewritten by the specific routine later.
	 *
	 * The Device Selection protocol is defined as follows:
	 * HOST: Read Status or AltStatus register
	 * HOST: Continue reading until BSY = 0, and DRQ = 0
	 * HOST: Write Device/Head register with appropriate DEV bit value
	 * HOST: Wait 400ns
	 * HOST: Read Status or AltStatus register
	 * HOST: Continue reading until BSY = 0, and DRQ = 0
	 *
     */
//	[self waitForDeviceIdle];	// Devices should be already in an idle state
    dh = _drives[_driveNum].addressMode;
    dh |= (_driveNum ? SEL_DRIVE1 : SEL_DRIVE0);
    outb(_ideRegsAddrs.drHead, dh);
	IODelay(1);
//	[self waitForDeviceIdle];	// Each method below will do their own wait

    ideIoReq->status = IDER_CMD_ERROR;
    ideIoReq->blocks_xfered = 0;
	
	[self clearInterrupts];

	switch (ideIoReq->cmd) {

	  case IDE_READ_DMA:
		if (((vm_offset_t)ideIoReq->addr & 0x03) == 0) {
	    block = ideIoReq->block;
	    cnt = ideIoReq->blkcnt;
		ddm_ide_log("IDE_READ_DMA: %d\n", cnt, 2, 3, 4, 5);
	    if (cnt > MAX_BLOCKS_PER_XFER) {
			ideIoReq->status = IDER_REJECT;
			break;
	    }

		ideIoReq->status = [self performDMA:ideIoReq];
		if (ideIoReq->status == IDER_SUCCESS)
		ideIoReq->blocks_xfered = ideIoReq->blkcnt;
	    break;
		}
		
		/*
		 * If we reached here, it means that the buffer is not 4-byte
		 * aligned. This should not happen.
		 */
		if (unaligned_warnings < UNALIGNED_WARNINGS_MAX) {
			IOLog("%s: READ DMA: buffer not 4-byte aligned\n", [self name]);
			unaligned_warnings++;
		}

	  case IDE_READ:
	  case IDE_READ_MULTIPLE:

	    block = ideIoReq->block;
	    cnt = ideIoReq->blkcnt;
		ddm_ide_log("IDE_READ: %d\n", cnt, 2, 3, 4, 5);
	    if (cnt > MAX_BLOCKS_PER_XFER)	{
			ideIoReq->status = IDER_REJECT;
			break;
	    }
	    ideIoReq->regValues = [self logToPhys:block numOfBlocks:cnt];
	    if (ideIoReq->cmd == IDE_READ)
		ideIoReq->status = 
			[self ideReadGetInfoCommon:&(ideIoReq->regValues) 
			client:(ideIoReq->map) 
			addr:(ideIoReq->addr) command:IDE_READ];
	    else
		ideIoReq->status =
		    [self ideReadMultiple:&(ideIoReq->regValues)
		     client:(ideIoReq->map) addr:(ideIoReq->addr)];

	    if (ideIoReq->status == IDER_SUCCESS)
		ideIoReq->blocks_xfered = ideIoReq->blkcnt;
	    break;

	  case IDE_WRITE_DMA:
		if (((vm_offset_t)ideIoReq->addr & 0x03) == 0) {
	    block = ideIoReq->block;
	    cnt = ideIoReq->blkcnt;
		ddm_ide_log("IDE_WRITE_DMA: %d\n", cnt, 2, 3, 4, 5);
	    if (cnt > MAX_BLOCKS_PER_XFER) {
			ideIoReq->status = IDER_REJECT;
			break;
	    }

		ideIoReq->status = [self performDMA:ideIoReq];
		if (ideIoReq->status == IDER_SUCCESS)
		ideIoReq->blocks_xfered = ideIoReq->blkcnt;
	    break;
		}

		/*
		 * If we reached here, it means that the buffer is not 4-byte
		 * aligned. This should not happen.
		 */
		if (unaligned_warnings < UNALIGNED_WARNINGS_MAX) {
			IOLog("%s: WRITE DMA: buffer not 4-byte aligned\n", [self name]);
			unaligned_warnings++;
		}

	  case IDE_WRITE:
	  case IDE_WRITE_MULTIPLE:

	    block = ideIoReq->block;
	    cnt = ideIoReq->blkcnt;
		ddm_ide_log("IDE_WRITE: %d\n", cnt, 2, 3, 4, 5);
	    if (cnt > MAX_BLOCKS_PER_XFER) {
			ideIoReq->status = IDER_REJECT;
			break;
	    }
	    ideIoReq->regValues = [self logToPhys:block numOfBlocks:cnt];
	    if (ideIoReq->cmd == IDE_WRITE)
		ideIoReq->status = [self ideWrite:&(ideIoReq->regValues)
					client:(ideIoReq->map)
					addr:(ideIoReq->addr)];
	    else
		ideIoReq->status = 
			[self ideWriteMultiple:&(ideIoReq->regValues)
			client:(ideIoReq->map) addr:(ideIoReq->addr)];

	    if (ideIoReq->status == IDER_SUCCESS)
		ideIoReq->blocks_xfered = ideIoReq->blkcnt;
	    break;

	  case IDE_SEEK:
	    block = ideIoReq->block;
	    cnt = 1;
	    ideIoReq->regValues = [self logToPhys:block numOfBlocks:cnt];
	    ideIoReq->status = 
	    		[self ideReadVerifySeekCommon:&(ideIoReq->regValues) 
				command:IDE_SEEK];
	    break;

	  case IDE_RESTORE:
	    ideIoReq->status = [self ideRestore:&(ideIoReq->regValues)];
	    break;

	  case IDE_READ_VERIFY:
	    block = ideIoReq->block;
	    cnt = ideIoReq->blkcnt;
		ddm_ide_log("IDE_READ_VERIFY: %d\n", cnt, 2, 3, 4, 5);
	    if (cnt > MAX_BLOCKS_PER_XFER) {
		ideIoReq->status = IDER_REJECT;
		break;
	    }
	    ideIoReq->regValues = [self logToPhys:block numOfBlocks:cnt];
	    ideIoReq->status = 
	    	[self ideReadVerifySeekCommon:&(ideIoReq->regValues)
				command:IDE_READ_VERIFY];
	    break;

	  case IDE_DIAGNOSE:
	    [self ideDiagnose:&error];
	    ideIoReq->diagResult = error;
	    ideIoReq->status = IDER_SUCCESS;
	    break;

	  case IDE_SET_PARAMS:
	    ideIoReq->status = 
	    		[self ideSetParams:_drives[_driveNum].ideInfo.sectors_per_trk
			numHeads:_drives[_driveNum].ideInfo.heads ForDrive:_driveNum];
	    break;

	  case IDE_IDENTIFY_DRIVE:
	    ideIoReq->status = 
	    		[self ideReadGetInfoCommon:&(ideIoReq->regValues)
			client :(ideIoReq->map) addr :(ideIoReq->addr)
			command:IDE_IDENTIFY_DRIVE];

	    if (ideIoReq->status == IDER_SUCCESS)
		ideIoReq->blocks_xfered = 1;
	    break;

	  case IDE_SET_MULTIPLE:
            maxSectors = (_drives[_driveNum].ideIdentifyInfo->multipleSectors) 
				& IDE_MULTI_SECTOR_MASK;
	    if (ideIoReq->maxSectorsPerIntr > maxSectors) {
		ideIoReq->status = IDER_REJECT;
	    } else {
		ideIoReq->status = [self ideSetMultiSectorMode:
				    &(ideIoReq->regValues)
				    numSectors:ideIoReq->maxSectorsPerIntr];
		if (ideIoReq->status == IDER_SUCCESS)
		    _drives[_driveNum].multiSector = ideIoReq->maxSectorsPerIntr;
		else
		    _drives[_driveNum].multiSector = 0;
	    }
	    break;

	  default:
	    ideIoReq->status = IDER_REJECT;
	    ddm_ide_lock("ideExecuteCmd: releasing lock, bad cmd\n",1,2,3,4,5);
	    [self ideCntrlrUnLock];
	    return (IDER_REJECT);
	}

	/*
	 * Return if command has been executed successfully or summarily
	 * rejected. 
	 */
	if (ideIoReq->status == IDER_SUCCESS)	{
	    [self ideCntrlrUnLock];
	    ddm_ide_lock("ideExecuteCmd: releasing lock, success\n",1,2,3,4,5);
	    return IDER_SUCCESS;
	}
	
	if (ideIoReq->status == IDER_REJECT)	{
	    [self ideCntrlrUnLock];
	    ddm_ide_lock("ideExecuteCmd: releasing lock, reject\n",1,2,3,4,5);
	    return IDER_REJECT;
	}

	/*
	 * The command failed to exceute properly but was accepted by the
	 * drive. Reset the drives and try again. 
	 */
	IOLog("%s: ATA command %x failed. Retrying...\n", [self name], 
		ideIoReq->cmd);
	[self getIdeRegisters:NULL Print:"ATA Command"];
	[self resetAndInit];
	
	/*
	 * resetAndInit will change the value of _driveNum.
	 * Revert _driveNum to the original value before retrying the
	 * command.
	 */
	_driveNum = drive;
	(void) [self ideRestore:&irv];
    }

    /*
     * If we get here then this is a catastrophic failure. 
     */
    ddm_ide_lock("ideExecuteCmd: releasing lock, failed\n",1,2,3,4,5);
    [self ideCntrlrUnLock];
    return ideIoReq->status;
}

@end

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.