File:  [Apple Darwin 0.x] / drvEIDE / EIDE.drvproj / EIDE.lksproj / IdeCnt.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.
 *
 * IdeCnt.m - IDE Controller class. 
 *
 * HISTORY 
 *
 * 1-Feb-1998	Joe Liu at Apple
 *	Created an initFromDeviceDescription method to do things that used to be
 *	done in the probe method. This allows us to call [super init...] after
 *	we have finished munging with our resources.
 *
 * 04-Sep-1996	 Becky Divinski at NeXT
 *	Added code in probe method to handle Dual EIDE personality case.
 * 
 * 07-Jul-1994	 Rakesh Dubey at NeXT
 *	Created from original driver written by David Somayajulu.
 */

#if 0
#define KERNEL		1
#define KERNEL_PRIVATE	1
#define ARCH_PRIVATE	1 
#undef	MACH_USER_API
#endif

#import "IdeCnt.h"
#import "IdeCntInit.h"
#import "IdeCntCmds.h"
#import "IdePIIX.h"
#import <driverkit/i386/IOPCIDeviceDescription.h>
#import <driverkit/i386/IOPCIDirectDevice.h>
#import <driverkit/kernelDriver.h>
#import <driverkit/interruptMsg.h>
#if (IO_DRIVERKIT_VERSION != 330)
#import <machdep/machine/pmap.h>        // XXX get rid of this
#endif
#import <mach/mach_interface.h>
#import <driverkit/IODevice.h>
#import <driverkit/align.h>
#import <machkit/NXLock.h>
#import <machdep/i386/io_inline.h>
#import <kernserv/i386/spl.h>
#ifdef NO_IRQ_MSG
#import <kern/sched_prim.h>
#endif NO_IRQ_MSG
#import <kern/lock.h>
#import "IdeDDM.h"
#import <bsd/stdio.h>

// XXX get rid of this
#if (IO_DRIVERKIT_VERSION != 330)
#import <machdep/machine/pmap.h>
#endif
#import "IdeCntInline.h"
#import "DualEide.h"

/*
 * If set to non-zero some debugging messages will be prinetd in case of
 * failure. 
 */
__private_extern__ unsigned int _ide_debug = 0;

/*
 * Config table keys that determine our configuration. 
 */
#define CONTROLLER_INSTANCE 	"Instance"
#define ATA_DEBUG 				"Debug"

#ifdef NO_IRQ_MSG
#warning Interrupt messaging OFF
typedef struct _ideStruct {
    @defs(IdeController)
} ideStruct_t;
#endif NO_IRQ_MSG

@implementation IdeController

/*
 * Create and initialize one instance of IDE Controller. 
 */

+ (BOOL)probe:(IODeviceDescription *)devDesc
{
    IdeController 	*idec;

	idec = [self alloc];
    if (idec == nil) {
		IOLog("%s: Failed to alloc instance\n", [self name]);
    	return NO;
    }
	
	return ([idec initFromDeviceDescription:devDesc] != nil);
}

- initFromDeviceDescription:(IODeviceDescription *)devDesc
{
    char			devName[20];
    const char  	*ctlType, *param;
    int     		n, hcUnitNum;
	
    IOEISADeviceDescription *deviceDescription = 
    			(IOEISADeviceDescription *)devDesc;

    if ((n = [deviceDescription numPortRanges]) != 1) {
		IOLog("ATA: Invalid port ranges: %d.\n", n);
		[self free];
		return nil;
	}
    
    if ((n = [deviceDescription numInterrupts]) != 1) {
		IOLog("ATA: Invalid number of interrupts: %d.\n", n);
		[self free];
		return nil;
	}

    ctlType = [[deviceDescription configTable] 
    			valueForStringKey: CONTROLLER_INSTANCE];

    hcUnitNum = (unsigned char) ctlType[0] - '0';

	// Hack for Dual EIDE case

	if (dualEide)
	{
		if (instanceNum > 0)
			hcUnitNum = instanceNum;
	}
    
    if (hcUnitNum > MAX_IDE_CONTROLLERS-1) {
		IOLog("ATA: Controller %d not supported.\n", hcUnitNum);
		[self free];
		return nil;
    }
    
    param = [[deviceDescription configTable] 
    			valueForStringKey: ATA_DEBUG];
    if ((param != NULL) && (strcmp(param, "Yes") == 0))	{
    	_ide_debug = 1;
		IOLog("ATA/ATAPI: Debug mode enabled.\n");
    } else {
    	_ide_debug = 0;
    }

    /*
     * Proceed with initialization. 
     */
    [self setUnit:hcUnitNum];
    sprintf(devName, "hc%d", hcUnitNum);
    [self setName:devName];
    [self setDeviceKind:"IdeController"];

	/*
	 * Allocate storage for the ideIdentifyInfo_t structures.
	 */
	for (n = 0; n < MAX_IDE_DRIVES; n++) {
		_drives[n].ideIdentifyInfo =
			(ideIdentifyInfo_t *)IOMalloc(sizeof(ideIdentifyInfo_t));
		if (_drives[n].ideIdentifyInfo == NULL) {
			[self free];
			return nil;
		}
	}

	/*
	 * This must be called before [super init...] so that we can append
	 * to the portRangeList before it is registered.
	 */
	if ([self probePCIController:(IOPCIDeviceDescription *)devDesc] != YES) {
		[self free];
		return nil;
    }
	
    if ([super initFromDeviceDescription:deviceDescription] == nil) {
		IOLog("%s: initFromDeviceDescription failed.\n", [self name]);
		[self free];
		return nil;
    }

    if ([self ideControllerInit:devDesc] != YES) {
		[self free];
		return nil;
    }

    if ([self registerDevice] == nil)	{
		IOLog("%s: Failed to register device.\n", [self name]);
		[self free];
		return nil;
    }
	
    return self;
}

static int logInterrupts = 0;
static unsigned short lastCommand = 0;

/*
 * Wait for interrupt or timeout. Returns IDER_SUCCESS or IDER_TIMEOUT. 
 */
- (ide_return_t)ideWaitForInterrupt:(unsigned int)command
			  ideStatus:(unsigned char *)status
{
#ifdef NO_IRQ_MSG
	ide_return_t	ret;
	u_int 			s;
	BOOL			_interruptOccurred;

	s = spldevice();

	assert_wait((void *)&waitQueue, TRUE);
	thread_set_timeout(HZ * _interruptTimeOut / 1000);	// value in ticks
	thread_block_with_continuation((void (*)()) 0);

	_interruptOccurred = interruptOccurred;

	splx(s);
	
	if (_interruptOccurred == YES)  {
		if (status != NULL)
			*status = inb(_ideRegsAddrs.status);	/* acknowledge */
		else
			inb(_ideRegsAddrs.status);

        logInterrupts -= 1;
        lastCommand = command;

		ret = IDER_SUCCESS;
    }
    else {
		IOLog("%s: interrupt timeout, cmd: 0x%0x\n", [self name], command);
		ret = IDER_CMD_ERROR;
	}

    return (ret);

#else  NO_IRQ_MSG

    msg_return_t result;
    msg_header_t msg;
    
	msg.msg_local_port = _ideInterruptPort;
    msg.msg_size = sizeof(msg);

#ifdef undef
        if (logInterrupts > 0)
	    IOLog("%s: waiting for interrupt for command %x\n", 
		    [self name], command);
#endif undef

    result = msg_receive(&msg, RCV_TIMEOUT, _interruptTimeOut);
    
    if (result == RCV_SUCCESS || result == RCV_TOO_LARGE) {
		if (status != NULL)
			*status = inb(_ideRegsAddrs.status);	/* acknowledge */
		else
			inb(_ideRegsAddrs.status);
	    
#ifdef undef
        if (logInterrupts > 0)
	    IOLog("%s: interrupt received for cmd: 0x%0x, status: 0x%0x\n", 
		    [self name], command, (status == NULL) ? 0 : *status);
#endif undef
        logInterrupts -= 1;
        lastCommand = command;

		return IDER_SUCCESS;
    }
    
    if (result == RCV_TIMED_OUT)
		IOLog("%s: interrupt timeout, cmd: 0x%0x\n", [self name], command);
    else
		IOLog("%s: Error %d in receiving interrupt, cmd: 0x%0x\n", 
			[self name], result, command);

    return IDER_CMD_ERROR;
#endif NO_IRQ_MSG
}

/*
 * Remove any interrupts messages that have queued up. This will get rid of
 * any spurious or duplicate interrupts. 
 */
- (void)clearInterrupts
{
#ifdef NO_IRQ_MSG
	return;
#else  NO_IRQ_MSG
    msg_return_t result;
    msg_header_t msg;
    int count;

    count = 0;

    while (1)	{

	msg.msg_local_port = _ideInterruptPort;
	msg.msg_size = sizeof(msg);
	
	result = msg_receive(&msg, RCV_TIMEOUT, 0);
	
	if (result != RCV_SUCCESS) {
	    if (count != 0)	{
#ifdef DEBUG
		IOLog("%s: %d spurious interrupts after command 0x%0x\n", 
			[self name], count, lastCommand);
#endif DEBUG
	    }
	    return;
	}
	count += 1;
    }
#endif NO_IRQ_MSG
}

- free
{
	int n;

	[self resetController];
			
	if ((_controllerID == PCI_ID_PIIX) ||
		(_controllerID == PCI_ID_PIIX3) ||
		(_controllerID == PCI_ID_PIIX4)) {
		if (_prdTable.ptr)
			IOFree(_prdTable.ptrReal, _prdTable.sizeReal);
	}
	
	for (n = 0; n < MAX_IDE_DRIVES; n++) {
		if (_drives[n].ideIdentifyInfo)
			IOFree(_drives[n].ideIdentifyInfo, sizeof(ideIdentifyInfo_t));
	}

	return [super free];
}

- (ide_return_t)waitForNotBusy
{
    int     delay = MAX_BUSY_DELAY;	/* microseconds */
    unsigned char status;

#ifdef undef
    IOLog("%s: waitForNotBusy\n", [self name]);
#endif undef

    delay -= 2;			/* Or else will block on second try */
    while (delay > 0) {
	status = inb(_ideRegsAddrs.status);
	if (!(status & BUSY)) {
	    return IDER_SUCCESS;
	}
	if (delay % 1000)	{
	    IODelay(2);
            delay -= 2;
	} else {
	    IOSleep(1);
	    delay -= 1000;
	}
	
#ifdef undef
	if ((_printWaitForNotBusy) && (delay % 20000 == 0))	{
	    IOLog("%s: waitForNotBusy, status = %x\n", 
	    	[self name], status);
	}
#endif undef
    }

    return IDER_TIMEOUT;
}

- (ide_return_t)waitForDeviceReady
{
    int     delay = MAX_BUSY_DELAY;
    unsigned char status;

#ifdef undef
    if (_printWaitForNotBusy)
	IOLog("%s: waitForDeviceReady\n", [self name]);
#endif undef
	
    delay -= 2;
    while (delay > 0) {
	status = inb(_ideRegsAddrs.status);
	if ((!(status & BUSY)) && (status & READY)) {
	    return IDER_SUCCESS;
	}
	if (delay % 1000)	{
	    IODelay(2);
            delay -= 2;
	} else {
	    IOSleep(1);
	    delay -= 1000;
	}
	
#ifdef undef
	if ((_printWaitForNotBusy) && (delay % 20000 == 0))	{
	    IOLog("%s: waitForDeviceReady, status = %x\n", 
	    	[self name], status);
	}
#endif undef

    }
    return IDER_TIMEOUT;
}

#define ATA_IDLE_BIT_MASK	(BUSY | DREQUEST)
- (ide_return_t)waitForDeviceIdle
{
    int     delay = MAX_BUSY_DELAY;
    unsigned char status;

#ifdef undef
	IOLog("%s: waitForDeviceIdle\n", [self name]);
#endif undef
	
    delay -= 2;
    while (delay > 0) {
		status = inb(_ideRegsAddrs.status);
		if ((status & ATA_IDLE_BIT_MASK) == 0)
			return IDER_SUCCESS;
		if (delay % 1000) {
			IODelay(2);
			delay -= 2;
		} else {
			IOSleep(1);
			delay -= 1000;
		}
    }
    return IDER_TIMEOUT;
}

- (ide_return_t)waitForDataReady
{
    int     delay = MAX_DATA_READY_DELAY;
    unsigned char status;

#ifdef undef
    IOLog("waitForDataReady\n");
#endif undef

    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_TIMEOUT;
}


/*
 * This is a private version for ideReadGetInfoCommon. It has a short delay
 * time. This is the same situation as atapiIdentifyDeviceWaitForDataReady in
 * AtapiCntCmds.m. The problem in both cases is that some devices pass
 * identification checks even when they should not. Now the first command
 * issued after can really confirm that but we don't want to wait too long. 
 */
- (ide_return_t)ataIdeReadGetInfoCommonWaitForDataReady
{
    int     delay = MAX_DATA_READY_DELAY/25;
    unsigned char status;

#ifdef undef
    IOLog("waitForDataReady\n");
#endif undef

    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_TIMEOUT;
}


- (void)enableInterrupts
{
    outb(_ideRegsAddrs.deviceControl, DISK_INTERRUPT_ENABLE);
}

- (void)disableInterrupts
{
    outb(_ideRegsAddrs.deviceControl, DISK_INTERRUPT_DISABLE);
}

- (void)setInterruptTimeOut:(unsigned int)timeOut
{
    _interruptTimeOut = timeOut;
}

- (unsigned int)interruptTimeOut
{
    return _interruptTimeOut;
}


/*
 * Return task file values, optionally print them if given a non-null string. 
 */
- (void)getIdeRegisters:(ideRegsVal_t *)rvp Print:(char *)printString
{
    ideRegsVal_t	ideRegs;
    
    ideRegs.error = inb(_ideRegsAddrs.error);
    ideRegs.sectCnt = inb(_ideRegsAddrs.sectCnt);
    ideRegs.sectNum = inb(_ideRegsAddrs.sectNum);
    ideRegs.cylLow = inb(_ideRegsAddrs.cylLow);
    ideRegs.cylHigh = inb(_ideRegsAddrs.cylHigh);
    ideRegs.drHead = inb(_ideRegsAddrs.drHead);
    ideRegs.status = inb(_ideRegsAddrs.altStatus);	/* don't ack */
    
    if (rvp != NULL)
	*rvp = ideRegs;
    
    if (printString != NULL)	{
	IOLog("%s: %s: error=0x%x secCnt=0x%x "
		"secNum=0x%x cyl=0x%x drhd=0x%x status=0x%x\n", 
		[self name], printString,
		ideRegs.error, ideRegs.sectCnt, ideRegs.sectNum,
		((ideRegs.cylHigh << 8) | ideRegs.cylLow),
		ideRegs.drHead, ideRegs.status);
    }
}

/*
 * Read maximum of one page from the sector buffer to "addr", write maximum
 * of one page from "addr" into the sector buffer. Note that (length <=
 * PAGE_SIZE). 
 */
- (void)xferData:(caddr_t)addr read:(BOOL)read client:(struct vm_map *)client
		length:(unsigned)length
{
    ideXferData(addr, read, client, length, _ideRegsAddrs, _transferWidth);
}

- (BOOL)isMultiSectorAllowed:(unsigned int)unit
{
    return (_drives[unit].multiSector != 0);
}

-(unsigned int)getMultiSectorValue:(unsigned int)unit
{
    return _drives[unit].ideIdentifyInfoSupported ?
		_drives[unit].multiSector : 0;
}

- (void)ideCntrlrLock
{
    [_ideCmdLock lock];
}

- (void)ideCntrlrUnLock
{
    [_ideCmdLock unlock];
}

/*
 * There is no need for separate ATAPI locks but we keep them for testing
 * purposes. 
 */
- (void)atapiCntrlrLock
{
    [_ideCmdLock lock];
}

- (void)atapiCntrlrUnLock
{
    [_ideCmdLock unlock];
}

/*
 * The ATAPI driver (CD-ROM or Tape) will scan thorugh this list of devices
 * looking for ATAPI hardware. 
 */
- (unsigned int)numDevices
{
    return MAX_IDE_DRIVES;
}

- (BOOL)isAtapiDevice:(unsigned char)unit
{
    if (unit >= MAX_IDE_DRIVES)
    	return NO;
	
    return _drives[unit].atapiDevice;
}

- (BOOL)isAtapiCommandActive:(unsigned char)unit
{
    return _drives[unit].atapiCommandActive;
}

- (void)setAtapiCommandActive:(BOOL)state forUnit:(unsigned char)unit
{
    _drives[unit].atapiCommandActive = state; 
}

- (BOOL) isDmaSupported:(unsigned int)unit
{
	return (_drives[unit].transferType != IDE_TRANSFER_PIO);
}

/*
 * Simply forward the interrupt. 
 */
static void ideInterruptHandler(void *identity, void *state, unsigned int arg)
{
#ifdef NO_IRQ_MSG
	ideStruct_t	*obj = (ideStruct_t *)arg;
	obj->interruptOccurred = YES;
	thread_wakeup_one(&obj->waitQueue);
#else  NO_IRQ_MSG
    IOSendInterrupt(identity, state, IO_DEVICE_INTERRUPT_MSG);
#endif NO_IRQ_MSG
}

- (BOOL)getHandler:(IOEISAInterruptHandler *)handler
		level:(unsigned int *)ipl
		argument:(unsigned int *)arg
		forInterrupt:(unsigned int)localInterrupt
{
    *handler = ideInterruptHandler;
    *ipl = IPLDEVICE;
    *arg = (unsigned int)self;    
    return YES;
}

/*
 * Power management. 
 */
- (idePowerState_t)drivePowerState
{
    return _drivePowerState;
}

- (void)setDrivePowerState:(idePowerState_t)state
{
    _drivePowerState = state;
}

- (void)putDriveToSleep
{
    if ([self drivePowerState] == IDE_PM_SLEEP)
    	return;
	
    _driveSleepRequest = YES;
    ddm_ide_lock("putDriveToSleep: acquiring lock, suspend\n",1,2,3,4,5);
    [self ideCntrlrLock];
    ddm_ide_lock("putDriveToSleep: acquired lock, suspend\n",1,2,3,4,5);
#ifdef DEBUG
    IOLog("%s: Entering suspend mode.\n", [self name]);
#endif DEBUG
    [self setDrivePowerState:IDE_PM_SLEEP];
}

/*
 * The method wakeUpDrive simply sets a flag. The drive is actually woken up
 * the next time we need it. This is done by a call from
 * ideExecuteCmd:ToDrive: method to the spinUpDrive:unit: method below. 
 */
- (void)wakeUpDrive
{
    if ([self drivePowerState] == IDE_PM_ACTIVE)
    	return;
	
    _driveSleepRequest = NO;
    ddm_ide_lock("wakeUpDrive: Exiting suspend mode.\n", 1,2,3,4,5);
    [self setDrivePowerState:IDE_PM_STANDBY];
    ddm_ide_lock("wakeUpDrive: releasing lock, recovered\n",1,2,3,4,5);    
    [self ideCntrlrUnLock];
}

/*
 * This will get the drive to spin up. We simply try to read a single sector
 * and are prepared to camp out for a very long time (>>30 secs). 
 */
#define MAX_MEDIA_ACCESS_TRIES		30

- (BOOL)spinUpDrive:(unsigned int)unit
{
    unsigned char status;
    int i, j;
    unsigned char dh;

    dh = _drives[unit].addressMode | (unit ? SEL_DRIVE1 : SEL_DRIVE0);

    for (i = 0; i < MAX_MEDIA_ACCESS_TRIES; i++)	{
    
	[self ideReset];
	[self disableInterrupts];
    
       /*
	* Read one sector at (0,0,1). (LBA == 1).
	*/
	outb(_ideRegsAddrs.drHead, dh);
	outb(_ideRegsAddrs.sectNum, 0x1);
	outb(_ideRegsAddrs.sectCnt, 0x1);
	outb(_ideRegsAddrs.cylLow, 0x0);
	outb(_ideRegsAddrs.cylHigh, 0x0);
	outb(_ideRegsAddrs.command, IDE_READ);   

	/*
	 * Wait for about ten seconds for response from drive. 
	 */
	for (j = 0; j < 5; j++)	{
            IOSleep(2000);
	    status = inb(_ideRegsAddrs.status);
	    if ((!(status & BUSY)) && (status & DREQUEST))	{
		[self ideReset];	/* FIXME: may not be needed */
		IOLog("%s: Recovered from suspend mode.\n", [self name]);
		return YES;
	    }
	}
	IOLog("%s: Retrying to recover from suspend mode.\n", [self name]);
    }

    IOLog("%s: Failed to recover from suspend mode.\n", [self name]);
    return NO;
}

/*
 * We need to get the ATA drives spinning before we can try to reset them.
 * The situation gets complicated if the ATA and ATAPI device share the same
 * cable. We basically start up all devices on this controller. 
 */
- (BOOL)startUpAttachedDevices
{
    int i;
    BOOL status = YES;
    
    /* Spin up only ATA drives */
    for (i = 0; i < MAX_IDE_DRIVES; i++)	{
	if ((_drives[i].ideInfo.type != 0) && ([self isAtapiDevice:i] == NO)) {
	    status = [self spinUpDrive:i];
	}
    }
    
    [self resetAndInit];
    [self setDrivePowerState:IDE_PM_ACTIVE];
    
    return status;
}

- (IOReturn) getPowerState:(PMPowerState *)state_p
{
    return IO_R_UNSUPPORTED;
}

- (IOReturn) setPowerState:(PMPowerState)state
{
    if (state == PM_READY) {
	[self wakeUpDrive];
    } else if (state == PM_SUSPENDED)	{
	[self putDriveToSleep];
    } else {
        //IOLog("%s: unknown APM event %x\n", [self name], (unsigned int)state);
    }
    
    return IO_R_SUCCESS;
}

- (IOReturn) getPowerManagement:(PMPowerManagementState *)state_p
{
    return IO_R_UNSUPPORTED;
}

- (IOReturn) setPowerManagement:(PMPowerManagementState)state
{
    return IO_R_UNSUPPORTED;
}

- property_IODeviceClass:(char *)classes length:(unsigned int *)maxLen
{
    strcpy( classes, IOClassIDEController);
    return( self);
}

- property_IODeviceType:(char *)types length:(unsigned int *)maxLen
{
    strcat( types, " "IOTypeIDE);
    return( self);
}

@end

unix.superglobalmegacorp.com

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