File:  [Apple Darwin 0.x] / drvEIDE / EIDE.drvproj / EIDE.lksproj / IdePIIX.m
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 17:40:59 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.
 *
 * IdePIIX.m - PIIX/PCI specific ATA controller initialization module. 
 *
 * 23-Jan-1998 Joe Liu at Apple
 *  Added support for PIIX/PIIX3/PIIX4 PCI IDE controllers.
 *
 * 05-Apr-1995	Rakesh Dubey at NeXT
 *	Fixed some bugs in PCI support.
 * 03-Oct-1994 	Rakesh Dubey at NeXT
 *      Created. 
 */

#import "IdeCnt.h"
#import "IdePIIX.h"
#import "IdeCntCmds.h"
#import <driverkit/i386/IOPCIDeviceDescription.h>
#import <driverkit/i386/IOPCIDirectDevice.h>
#import <driverkit/generalFuncs.h>
#import <driverkit/kernelDriver.h>
#import <driverkit/interruptMsg.h>
#import <mach/mach_interface.h>
#import <machdep/i386/io_inline.h>
#import <string.h>
#import <stdio.h>
#import "IdeDDM.h"
#import "PIIX.h"
#import "PIIXTiming.h"
#import "IdeShared.h"

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

extern vm_offset_t pmap_resident_extract(pmap_t pmap, vm_offset_t va);

//#define DEBUG

#ifndef MIN
#define MIN(a,b)    ((a) < (b) ? (a) : (b))
#endif  MIN

#ifndef MAX
#define MAX(a,b)    ((a) > (b) ? (a) : (b))
#endif  MAX

/*
 * Function: IOMallocPage
 *
 * Purpose:
 *   Returns a pointer to a page-aligned memory block of size >= PAGE_SIZE.
 *   Note that on Intel, the hardware page size of 4K. However, MACH's
 *   notion of a page is 8K, which is comprised of two contiguous
 *   (physical/virtual) hardware pages.
 *
 * Return:
 *   Actual pointer and size of block returned in actual_ptr and actual_size.
 *   Use these as arguments to IOFree: IOFree(*actual_ptr, *actual_size);
 */
static void *
IOMallocPage(int request_size, void ** actual_ptr,
				 int * actual_size)
{
    void * mem_ptr;
    
	/*
	 * Minimize memory use by first trying to allocate the requested size
	 * without any padding.
	 */
	*actual_size = round_page(request_size);
	mem_ptr = IOMalloc(*actual_size);
	if (mem_ptr == NULL)
		return NULL;
	
	/*
	 * Check alignment of this page.
	 */
	if ((vm_offset_t)mem_ptr & (PAGE_SIZE - 1)) {	// NOT page aligned.
		IOFree(mem_ptr, *actual_size);
		*actual_size = round_page(request_size) + PAGE_SIZE;
		mem_ptr = IOMalloc(*actual_size);
		if (mem_ptr == NULL)
			return NULL;		
	}

	*actual_ptr = mem_ptr;
	return ((void *)round_page(mem_ptr));		
}

@implementation IdeController(PIIX)

/*
 * Method: probePCIController
 *
 * Purpose:
 * Probe the existence of a supported PCI chipset, then proceed to
 * record the PCI IDE controller found.
 *
 * Note:
 * This is called before [super init...], and so we use the class version
 * of getPCIdevice method calls.
 *
 */
- (BOOL) probePCIController:(IOPCIDeviceDescription *)devDesc
{
    unsigned char 	devNum, funcNum, busNum;
    const char 		*value;
	const char		*deviceName;
	IOConfigTable 	*configTable;
    IOReturn 		rtn;
    const id self_class = [self class];

	/*
	 * Initialize PCI ivars.
	 */
	_controllerID = PCI_ID_NONE;
	_ideChannel   = PCI_CHANNEL_OTHER;
	_busMaster    = NO;
	bzero((char *)&_prdTable, sizeof(_prdTable));
	
	/*
	 * Make sure we are dealing with a PCI config table by reading the
	 * BUS_TYPE key.
	 */
    configTable = [devDesc configTable];
    value = [configTable valueForStringKey:BUS_TYPE];
    if (!value || strcmp(value, "PCI") != 0) {
		// Not PCI, return YES to continue probing for non PCI controllers.
		return YES;
    }
    
	/*
	 * Read PCI config space for VendorID and DeviceID.
	 */
    rtn = [devDesc getPCIdevice:&devNum function:&funcNum bus:&busNum];
    if (rtn != IO_R_SUCCESS) {
    	IOLog("%s: Unsupported PCI hardware\n", [self name]);
		return NO;
    }
    rtn = [self_class getPCIConfigData:&_controllerID atRegister:0x00
		withDeviceDescription:devDesc];
    if (rtn != IO_R_SUCCESS)	{
    	IOLog("%s: PCI config space access error %d\n", [self name], rtn);
		return NO;
    }	  

	switch (_controllerID) {
		case PCI_ID_PIIX:
			deviceName = "PIIX";
			break;
		case PCI_ID_PIIX3:
			deviceName = "PIIX3";
			break;
		case PCI_ID_PIIX4:
			deviceName = "PIIX4";
			break;
		default:
			IOLog("%s: Unknown PCI IDE controller (0x%08lx)\n",
				[self name], _controllerID);
			_controllerID = PCI_ID_NONE;
			return NO;
	}

	/*
	 * Report the PCI controller found.
	 */
	IOLog("%s: %s PCI IDE Controller at Dev:%d Func:%d Bus:%d\n",
		[self name], deviceName, devNum, funcNum, busNum);

	/*
	 * At this point, we are certain that we are dealing with a
	 * Intel PIIX class controller.
	 */
	return ([self PIIXInitController:devDesc]);
}

/*
 * Method: initPIIXController
 *
 * Initializes the Intel PIIX IDE controller.
 */
- (BOOL) PIIXInitController:(IOPCIDeviceDescription *)devDesc
{
	piix_idetim_u	idetim;
	IOReturn 		rtn;
	unsigned long	configReg;
	const id self_class = [self class];

	/*
	 * Are we initializing the primary or the secondary channel?
	 * Set the ivar _ideChannel.
	 */
	switch ([devDesc portRangeList]->start) {
		case PIIX_P_CMD_ADDR:
			_ideChannel = PCI_CHANNEL_PRIMARY;
			break;
		case PIIX_S_CMD_ADDR:
			_ideChannel = PCI_CHANNEL_SECONDARY;
			break;
		default:
			_ideChannel = PCI_CHANNEL_OTHER;
	}
	
	/*
	 * PIIX configured on a weird location, cannot continue.
	 *
	 * NOTE:
	 * The I/O ranges does NOT show up as a I/O range in the PCI
	 * configuration space. However, the Bus-Mastering I/O range
	 * does show up at configuration space location 0x20.
	 */
	if ((_ideChannel == PCI_CHANNEL_OTHER) ||
		([devDesc portRangeList]->size != PIIX_CMD_SIZE)) {
		IOLog("%s: Invalid IDE Command Block set to 0x%x size %d\n",
			[self name],
			[devDesc portRangeList]->start,
			[devDesc portRangeList]->size);
		return NO;
	}
	
	/*
	 * Verify our IRQ assignment.
	 *
	 * PIIX hardcodes the following settings:
	 * IRQ 14 - primary channel
	 * IRQ 15 - secondary channel
	 */
	{
	unsigned int irq;
	
	irq = (_ideChannel == PCI_CHANNEL_PRIMARY) ? PIIX_P_IRQ : PIIX_S_IRQ;
	if ([devDesc interrupt] != irq) {
		IOLog("%s: Invalid IRQ: %d\n", [self name], [devDesc interrupt]);
		return NO;
	}
	}
	
	/*
	 * Check the I/O Space Enable bit in the PCI command register.
	 *
	 * This is the master enable bit for the PIIX controller.
	 */
    rtn = [self_class getPCIConfigData:&configReg atRegister:PIIX_PCICMD
		withDeviceDescription:devDesc];
    if (rtn != IO_R_SUCCESS)	{
    	IOLog("%s: PCI config space access error %d\n", [self name], rtn);
		return NO;
    }	
	if (!(configReg & 0x0001)) {
		IOLog("%s: PCI IDE controller is not enabled\n", [self name]);
		return NO;
	}
	if (configReg & 0x0004)
		_busMaster = YES;
	else
		_busMaster = NO;
	
	/*
	 * Fetch the corresponding primary/secondary IDETIM register and
	 * verify that the individual channels are enabled.
	 */
    rtn = [self_class getPCIConfigData:&configReg atRegister:PIIX_IDETIM
		withDeviceDescription:devDesc];
    if (rtn != IO_R_SUCCESS)	{
    	IOLog("%s: PCI config space access error %d\n", [self name], rtn);
		return NO;
    }
	if (_ideChannel == PCI_CHANNEL_SECONDARY)
		configReg >>= 16;	// PIIX_IDETIM + 2 for secondary channel
	idetim.word = (u_short)configReg;
	
	if (!idetim.bits.ide) {
		IOLog("%s: %s PCI IDE channel is not enabled\n",
			[self name],
			(_ideChannel == PCI_CHANNEL_PRIMARY) ? "Primary" : "Secondary");
		return NO;
	}

	/*
	 * Register and record the location of our Bus Master 
	 * interface registers.
	 */
	if (_busMaster && ([self PIIXRegisterBMRange:devDesc] == NO)) {
		IOLog("%s: Bus master I/O range registration failed\n",
			[self name]);
		_busMaster = NO;
	}
	
	/*
	 * Allocate a 4K-page (perhaps 8K) aligned page of memory for
	 * the PRD table.
	 */
	if (_busMaster && ([self PIIXInitPRDTable] == NO)) {
		IOLog("%s: cannot allocate memory for descriptor table\n",
			[self name]);
		_busMaster = NO;
	}

#if 0
	IOLog("%s: PCI bus master DMA: %s\n",
		[self name], busMaster ? "Enabled" : "Disabled");
#endif

	/*
	 * Revert to default timing.
	 */
	[self PIIXResetTimings:devDesc];
	
    return YES;
}

/*
 * Method: getPCIControllerCapabilities
 *
 * Return the capability of the PCI IDE controller in 'm'.
 *
 */
- (void) getPCIControllerCapabilities:(txferModes_t *)m
{
	m->mode.swdma = m->mode.mwdma = m->mode.udma = ATA_MODE_NONE;
	switch (_controllerID) {
		case PCI_ID_PIIX:
		case PCI_ID_PIIX3:
		case PCI_ID_PIIX4:
			m->mode.pio   = ata_mode_to_mask(ATA_MODE_4);
			if (_busMaster) {
				m->mode.mwdma = ata_mode_to_mask(ATA_MODE_2);
				if (_controllerID == PCI_ID_PIIX4)
					m->mode.udma = ata_mode_to_mask(ATA_MODE_2);
			}
			break;
	}
}

/*
 * Get the PIO port transfer width. This refers to the width of the
 * I/O transfer on the PIO port, the IDE bus width is always 16-bits.
 *
 * All PIIX controllers are capable of 32-bit transfers on the data
 * port.
 */
- (ideTransferWidth_t) getPIOTransferWidth
{
	return (IDE_TRANSFER_32_BIT);
}

/*
 * Method: resetPCIController
 *
 * Not a true RESET, simply return the PCI controller to a quiescent state
 * and return all IDE ports to the default timing.
 */
- (void) resetPCIController
{
	switch (_controllerID) {
		case PCI_ID_PIIX:
		case PCI_ID_PIIX3:
		case PCI_ID_PIIX4:
			[self PIIXInit];
			[self PIIXResetTimings:[self deviceDescription]];
			break;
	}
}

/*
 * Method: PIIXResetTimings
 *
 * Purpose:
 * Revert the timing register to the default value. The transfer timing
 * is set to the compatible mode. We need to be careful to initialize the
 * register only for our current IDE channel.
 */
- (void) PIIXResetTimings:(IOPCIDeviceDescription *)devDesc
{
	union {
		u_long dword;
		struct {
			piix_idetim_u pri;
			piix_idetim_u sec;
		} tim;
	} timings;

    IOReturn rtn;
	u_long udma;

	/*
	 * Read the PIIX_IDETIM register.
	 */	
	rtn = [[self class] getPCIConfigData:&timings.dword
		atRegister:PIIX_IDETIM
		withDeviceDescription:devDesc];
    if (rtn != IO_R_SUCCESS)
		return;

	/*
	 * Read both PIIX_UDMACTL and PIIX_UDMATIM register.
	 */
	rtn = [[self class] getPCIConfigData:&udma atRegister:PIIX_UDMACTL
		withDeviceDescription:devDesc];

	/*
	 * Set compatible timing.
	 * Disable UDDMA and set its timing registers to the slowest mode.
	 */	
	switch (_ideChannel) {
		case PCI_CHANNEL_PRIMARY:
			timings.tim.pri.word &= 0x8000;
			udma &= 0xffccfffc;
			break;
		case PCI_CHANNEL_SECONDARY:
			timings.tim.sec.word &= 0x8000;
			udma &= 0xccfffff3;
			break;
		default:
			return;
	}
	
	/*
	 * Write the modified PCI config space registers back.
	 */
	[[self class] setPCIConfigData:timings.dword atRegister:PIIX_IDETIM
		withDeviceDescription:devDesc];
	[[self class] setPCIConfigData:udma atRegister:PIIX_UDMACTL
		withDeviceDescription:devDesc];
}

/*
 * Method: PIIXRegisterBMRange:
 *
 * Purpose:
 * Add the 8-byte Bus-Master control registers to the portRangeList in
 * the deviceDescription. The base address for the registers resides in
 * PCI config space location 0x20. The first 8 bytes are for the primary
 * IDE channel, the next eight bytes are for the secondary IDE channel.
 *
 * Note:
 * This must be called before [super init...] because that's when the
 * resources are registered.
 */
- (BOOL) PIIXRegisterBMRange:(IOPCIDeviceDescription *)devDesc
{
    IOReturn 		rtn;
	unsigned long	bmiba;
	IORange 		io_range[2];
	
    rtn = [[self class] getPCIConfigData:&bmiba atRegister:PIIX_BMIBA
		withDeviceDescription:devDesc];
    if (rtn != IO_R_SUCCESS) {
    	IOLog("%s: PCI config space access error %d\n", [self name], rtn);
		return NO;
    }
	
	/*
	 * Sanity check. Make sure this is an I/O range.
	 */
	if ((bmiba & 0x01) == 0) {
		IOLog("%s: PCI memory range 0x%02x (0x%08lx) is not an I/O range\n",
			[self name], PIIX_BMIBA, bmiba);
		return NO;
	}
	
	_bmRegs = bmiba & PIIX_BM_MASK;

	if (_bmRegs == 0)	// uninitialized range
		return NO;

	if (_ideChannel == PCI_CHANNEL_SECONDARY)
		_bmRegs += PIIX_BM_OFFSET;
	
	/*
	 * Add this range to our device description's port range list.
	 */
	io_range[0] = [devDesc portRangeList][0];
	io_range[1].start = _bmRegs;
	io_range[1].size  = PIIX_BM_SIZE;
	if ([devDesc setPortRangeList:io_range num:2] != IO_R_SUCCESS) {
		IOLog("%s: setPortRangeList failed\n", [self name]);
		return NO;
	}
	
	return YES;
}

/*
 * Method: PIIXInitPRDTable
 *
 * Purpose:
 * Initialize a "page-aligned" page of memory for the PRD descriptors
 * used by the bus master IDE controller.
 *
 * FIXME: Need to free the _prdTable memory.
 */
- (BOOL) PIIXInitPRDTable
{
	_prdTable.size = PAGE_SIZE;
	_prdTable.ptr = (void *)IOMallocPage(
						_prdTable.size,
						&_prdTable.ptrReal,
						&_prdTable.sizeReal
						);

	/*
	 * _prdTable->ptr should now points to a physically contiguous block
	 * of PAGE_SIZE bytes.
	 */
    if (_prdTable.ptr == NULL)
		return NO;
	
	/*
	 * cache the physical address of the descriptor table to _tablePhyAddr.
	 */
	if (IOPhysicalFromVirtual(IOVmTaskSelf(), (vm_address_t)_prdTable.ptr,
		&_tablePhyAddr) != IO_R_SUCCESS) {
		IOFree(_prdTable.ptrReal, _prdTable.sizeReal);
		return NO;
	}

	bzero(_prdTable.ptr, _prdTable.size);
	return YES;
}

/*
 * Method: PIIXReportTimings:slaveTiming:isPrimary:
 *
 * Purpose:
 * Log the drive timings set in the two PIIX timing registers.
 * The units for the values are in PCI clocks.
 */
- (void) PIIXReportTimings:(piix_idetim_u)tim
              slaveTiming:(piix_sidetim_u)stim
                isPrimary:(BOOL)primary
{
	if (!_ide_debug)
		return;

	IOLog("%s: Drive 0: ISP:%d Clks RCT:%d Clks\n",
		[self name],
		PIIX_ISP_TO_CLK(tim.bits.isp),
		PIIX_RCT_TO_CLK(tim.bits.rct));
#if 0
	IOLog("%s: Drive 0 Fast timing DMA only: %s\n", [self name],
		tim.bits.dte0 ? "on" : "off");
	IOLog("%s: Drive 0 Prefetch and Posting: %s\n", [self name],
		tim.bits.ppe0 ? "on" : "off");
	IOLog("%s: Drive 0 IORDY sample enable : %s\n", [self name],
		tim.bits.ie0 ? "on" : "off");
	IOLog("%s: Drive 0 Fast timing enable  : %s\n", [self name],
		tim.bits.time0 ? "on" : "off");
#endif 0
	IOLog("%s: Drive 1: ISP:%d Clks RCT:%d Clks\n", [self name],
		tim.bits.sitre ?
			(primary ?
				PIIX_ISP_TO_CLK(stim.bits.pisp1) : 
				PIIX_ISP_TO_CLK(stim.bits.sisp1)) : 
			PIIX_ISP_TO_CLK(tim.bits.isp),
		tim.bits.sitre ?
			(primary ?
				PIIX_RCT_TO_CLK(stim.bits.prct1) :
				PIIX_RCT_TO_CLK(stim.bits.srct1)) :
			PIIX_RCT_TO_CLK(tim.bits.rct));
#if 0
	IOLog("%s: Drive 1 Fast timing DMA only: %s\n", [self name],
		tim.bits.dte1 ? "on" : "off");
	IOLog("%s: Drive 1 Prefetch and Posting: %s\n", [self name],
		tim.bits.ppe1 ? "on" : "off");
	IOLog("%s: Drive 1 IORDY sample enable : %s\n", [self name],
		tim.bits.ie1 ? "on" : "off");
	IOLog("%s: Drive 1 Fast timing enable  : %s\n", [self name],
		tim.bits.time1 ? "on" : "off");
#endif 0
}

/*
 * Method: setPCIControllerCapabilities
 *
 * Purpose:
 * Based on the transfer modes and types for both IDE drives, setup the
 * controller to support those modes.
 *
 */
- (BOOL) setPCIControllerCapabilitiesForDrives:(driveInfo_t *)drives
{
	IOPCIConfigSpace configSpace;
	
	if (_controllerID == PCI_ID_NONE)
		return NO;
	
	[self getPCIConfigSpace:&configSpace];
	
	switch (_controllerID) {
		case PCI_ID_PIIX:
		case PCI_ID_PIIX3:
		case PCI_ID_PIIX4:
			[self PIIXComputePCIConfigSpace:&configSpace forDrives:drives];
			break;
		default:
			return NO;
	}

	[self setPCIConfigSpace:&configSpace];
    return YES;
}

/*
 * Method: computePCIConfigSpaceForPIIX:modes:
 *
 * Purpose:
 * Set the IDETIM and the SIDETIM IDE timing registers based on the
 * PIO modes supported by the two drives on the IDE channel.
 */
- (void) PIIXComputePCIConfigSpace:(IOPCIConfigSpace *)configSpace
		forDrives:(driveInfo_t *)drv
{
	u_char *pci_space = (u_char *)configSpace;
    piix_idetim_u	*idetim;
	piix_sidetim_u  *sidetim;
	piix_udmactl_u	*udmactl;
	piix_udmatim_u  *udmatim;
	unsigned char	modeDrive0;
	unsigned char	modeDrive1;
	u_char 			isp, rct;

	if (MAX_IDE_DRIVES != 2)
		return;
	
	switch (_ideChannel) {
		case PCI_CHANNEL_PRIMARY:
			idetim = (piix_idetim_u	*)&pci_space[PIIX_IDETIM];
			break;
		case PCI_CHANNEL_SECONDARY:
			idetim = (piix_idetim_u	*)&pci_space[PIIX_IDETIM_S];
			break;
		default:
			IOLog("%s: PIIX: Unknown IDE channel\n", [self name]);
			return;
	}
	sidetim = (piix_sidetim_u *)&pci_space[PIIX_SIDETIM];
	udmactl = (piix_udmactl_u *)&pci_space[PIIX_UDMACTL];
	udmatim = (piix_udmatim_u *)&pci_space[PIIX_UDMATIM];

	modeDrive0 = ata_mode_to_num(drv[0].transferMode);
	modeDrive1 = ata_mode_to_num(drv[1].transferMode);
	
	/* Enable slave timing if timings are different and
	 * a slave device is present.
	 */
	idetim->bits.sitre = 0;
	isp = PIIXGetISPForMode(modeDrive0, drv[0].transferType);
	rct = PIIXGetRCTForMode(modeDrive0, drv[0].transferType);
	
	if ((PIIXGetCycleForMode(modeDrive0, drv[0].transferType) != 
		PIIXGetCycleForMode(modeDrive1, drv[1].transferType)) &&
		(drv[1].ideInfo.type != 0)) {
		if (_controllerID == PCI_ID_PIIX) {
			/* Do not have the luxury of separate timing register for
			 * drive 0 and drive 1. Use the minimum of the two PIO modes.
			 * Or, the max of the two timings.
			 */
			isp = MAX(PIIXGetISPForMode(modeDrive0, drv[0].transferType),
				      PIIXGetISPForMode(modeDrive1, drv[1].transferType));
			rct = MAX(PIIXGetRCTForMode(modeDrive0, drv[0].transferType),
				      PIIXGetRCTForMode(modeDrive1, drv[1].transferType));			
		}
		else
			idetim->bits.sitre = 1;		// enable slave timing
	}
	
	/*
	 * Reset all performance tuning bits and disable UDMA.
	 */
	idetim->word &= 0xc000;
	switch (_ideChannel) {
		case PCI_CHANNEL_PRIMARY:
			udmactl->bits.psde0 = 0;
			udmactl->bits.psde1 = 0;
			break;
		default:
			udmactl->bits.ssde0 = 0;
			udmactl->bits.ssde1 = 0;
	}

	/*
	 * Set the timings for drive 0 (master).
	 */
	if (drv[0].ideInfo.type == 0) {
		IOLog("%s: Drive 0 is not present\n", [self name]);
		return;
	}

	/*
	 * Set timings for Drive 0 (Master drive).
	 */
	if (drv[0].transferType == IDE_TRANSFER_ULTRA_DMA) {
		if (modeDrive0 > 2) modeDrive0 = 2;
		switch (_ideChannel) {
			case PCI_CHANNEL_PRIMARY:
				udmactl->bits.psde0 = 1;
				udmatim->bits.pct0 = modeDrive0;
				break;
			case PCI_CHANNEL_SECONDARY:
				udmactl->bits.ssde0 = 1;
				udmatim->bits.sct0 = modeDrive0;
				break;
			default:
				break;
		}
	}
	idetim->bits.isp = isp;
	idetim->bits.rct = rct;
	
	/* Set timings for drive 1 (Slave drive).
	 */
	if (drv[1].transferType == IDE_TRANSFER_ULTRA_DMA) {
		if (modeDrive1 > 2) modeDrive1 = 2;
		switch (_ideChannel) {
			case PCI_CHANNEL_PRIMARY:
				udmactl->bits.psde1 = 1;
				udmatim->bits.pct1 = modeDrive1;
				break;
			case PCI_CHANNEL_SECONDARY:
				udmactl->bits.ssde1 = 1;
				udmatim->bits.sct1 = modeDrive1;
				break;
			default:
				break;
		}
	}
	if (idetim->bits.sitre) {
		isp = PIIXGetISPForMode(modeDrive1, drv[1].transferType);
		rct = PIIXGetRCTForMode(modeDrive1, drv[1].transferType);	
		if (_ideChannel == PCI_CHANNEL_PRIMARY) {
			sidetim->bits.pisp1 = isp;
			sidetim->bits.prct1 = rct;
		}
		else {
			sidetim->bits.sisp1 = isp;
			sidetim->bits.srct1 = rct;
		}
	}

	/*
	 * Enable fast timings. Turn on IORDY sampling always?
	 */
	idetim->bits.time0 = 1;
	idetim->bits.ppe0  = 1;
	idetim->bits.ie0   = 1;
	if (drv[1].ideInfo.type != 0) {
		idetim->bits.time1 = 1;
		idetim->bits.ppe1  = 1;
		idetim->bits.ie1   = 1;
	}

	/*
	 * For DMA, disable fast timing for PIO.
	 */
	if (drv[0].transferType != IDE_TRANSFER_PIO)
		idetim->bits.dte0 = 1;
	if (drv[1].transferType != IDE_TRANSFER_PIO)
		idetim->bits.dte1 = 1;

	[self PIIXReportTimings:*idetim slaveTiming:*sidetim 
		isPrimary:(_ideChannel == PCI_CHANNEL_PRIMARY)];

	return;
}

/*************************************************************************
 *
 * Intel PIIX/PIIX3/PIIX4 Bus-Mastering IDE DMA support
 *
 *************************************************************************/

/*
 * Function: PIIXVirtualToPhysical
 *
 * Similar to IOPhysicalFromVirtual but with no SPLVM/SPLX and locking.
 */
static __inline__ vm_offset_t
PIIXVirtualToPhysical(struct vm_map *map, vm_offset_t vaddr)
{
	return (vm_offset_t)pmap_resident_extract(
			(pmap_t)vm_map_pmap_EXTERNAL(map),
			vaddr);
}

/*
 * Function: PIIXStartDMA
 *
 * Purpose:
 * Start the bus master by writing a 1 to the SSBM bit in BMICX register.
 *
 * Argument:
 * piix_base - base address of the I/O space mapped bus master registers
 */
static __inline__ void
PIIXStartDMA(u_short piix_base)
{
	piix_bmicx_u piix_cmd;
	
	/*
	 * Engage the bus master by writing 1 to the start bit in the
	 * Command Register.
	 */
	piix_cmd.byte = inb(piix_base + PIIX_BMICX);
	piix_cmd.bits.ssbm = 1;
	outb(piix_base + PIIX_BMICX, piix_cmd.byte);
}

/*
 * Function: PIIXStopDMA
 *
 * Purpose:
 * Stop the bus master by clearing the SSBM bit in BMICX register.
 *
 * Argument:
 * piix_base - base address of the I/O space mapped bus master registers
 */
static __inline__ void
PIIXStopDMA(u_short piix_base)
{
	piix_bmicx_u piix_cmd;
	
	/*
	 * Stop the bus master by writing 0 to the start bit in the
	 * Command Register.
	 */
	piix_cmd.byte = inb(piix_base + PIIX_BMICX);
	piix_cmd.bits.ssbm = 0;
	outb(piix_base + PIIX_BMICX, piix_cmd.byte);	
}

/*
 * Function: PIIXGetStatus
 *
 * Purpose:
 * Return the PIIX BMISX (bus master IDE status register).
 *
 * Argument:
 * piix_base - base address of the I/O space mapped bus master registers
 */
static __inline__ u_char
PIIXGetStatus(u_short piix_base)
{
	return (inb(piix_base + PIIX_BMISX));
}
			
/*
 * Function: PIIXSetupPRDTable
 *
 * Purpose:
 * Setup the PRD (descriptor) table for the current IDE transfer.
 * This table must be aligned on a DWord (4 byte) boundary.
 *
 * Arguments:
 * table    - points to the start of the PRD table
 * size     - max number of PRD entries that the table can hold
 * vaddr	- virtual address of the start of memory buffer
 * size		- size of memory buffer in bytes
 * map		- vm_map for the memory buffer
 *
 * Return:
 *	YES: table is setup and ready for use
 *	NO : table full or alignment error
 *
 */
static __inline__ BOOL
PIIXSetupPRDTable(piix_prd_t *table, u_int table_size, vm_offset_t vaddr,
	u_int size, struct vm_map *map)
{
	vm_offset_t vaddr_next;
	vm_offset_t paddr;
	vm_offset_t paddr_next;
	vm_offset_t paddr_start;
	const char *name = "PIIXSetupPRDTable";
	u_int len_prd;
	u_int index;

#ifdef DEBUG	
	piix_prd_t *table_saved = table;
#endif DEBUG

	ddm_ide_dma("  PIIXSetupPRDTable: vaddr:%08x size:%d\n",
		(u_int)vaddr, (u_int)size, 3, 4, 5);

	if (vaddr & (PIIX_BUF_ALIGN - 1)) {
		IOLog("%s: buffer is not %d byte aligned\n", name, PIIX_BUF_ALIGN);
		return NO;
	}
	
	if (size == 0) {
		IOLog("%s: zero length DMA buffer\n", name);
		return NO;
	}
	
	index = len_prd = 0;
	paddr = PIIXVirtualToPhysical(map, vaddr);
	paddr_start = paddr;
	do {
		u_int len;

		vaddr_next = trunc_page(vaddr) + PAGE_SIZE;		// next virtual page
		paddr_next = trunc_page(paddr) + PAGE_SIZE;		// next phys page
		vaddr      = vaddr_next;
		
		len = paddr_next - paddr;		// length to transfer in this page
		if (len > size) len = size;		// take the minimum
		size  -= len;					// decrement total remaining bytes
		len_prd += len;					// increment current PRD counter

		/*
		 * If there are more bytes remaining, try to append the next
		 * page into the same PRD. We must check that the next page
		 * is physically contiguous with the current one.
		 *
		 * Each PRD cannot cross 64K boundary, and is limited to 64K per PRD.
		 */
		if (size && 
		(paddr_next == (paddr = PIIXVirtualToPhysical(map, vaddr))) &&
		((paddr_start & ~(PIIX_BUF_BOUND-1))==(paddr & ~(PIIX_BUF_BOUND-1))) &&
		(len_prd <= (PIIX_BUF_LIMIT - PAGE_SIZE))) {
		continue;
		}

		/*
		 * Setup PRD entry
		 *
		 * For the length field in PRD, 0 is used to denote the max
		 * transfer size of 64K.
		 */
		table->base = paddr_start;
		table->count = (len_prd == PIIX_BUF_LIMIT) ? 0 : len_prd;
		table->eot = 0;
		table++;

		len_prd = 0;
		paddr_start = paddr;

	} while (size && (++index < table_size));
	
	if (size) {
		IOLog("%s: PRD table exhausted\n", name);
		return NO;
	} 
	
	/*
	 * Set the 'end-of-table' bit on the last PRD entry.
	 */
	--table;
	table->eot = 1;

#ifdef DEBUG
	{
	int i = 0;
	u_int *ip = (u_int *)&table_saved[0];
	do {
		ddm_ide_dma("    table[%d]  %08x:%08x\n", i, *ip, *(ip+1), 4, 5);
		ip += 2;
	} while (i++ < index);
	}
#endif DEBUG

	return YES;
}

/*
 * Function: PIIXPrepareDMA
 *
 * Purpose:
 * Prepare the PIIX bus master for a DMA transfer.
 *
 * Arguments:
 *  piix_base - base address of the I/O space mapped bus master registers
 *  table     - points to the start of the PRD table
 *  tableAddr - physical address of the table
 *  isRead    - YES for read transfers (from device to host)
 */
static __inline__ BOOL
PIIXPrepareDMA(u_short piix_base, u_int tableAddr, BOOL isRead)
{
	piix_bmicx_u piix_cmd;
	piix_bmisx_u piix_status;

	/*
	 * Provide the starting address of the PRD table by loading the
	 * PRD Table Pointer Register.
	 *
	 * For some reason, outl(piix_base + PIIX_BMIDTPX, tableAddr)
	 * will only write the lower 16-bit WORD. That's why we use
	 * two outw instructions.
	 */	
	outw(piix_base + PIIX_BMIDTPX, tableAddr & 0xffff);
	outw(piix_base + PIIX_BMIDTPX + 2, (tableAddr >> 16) & 0xffff);

	/*
	 * Set the R/W bit depending on the direction of the transfer.
	 * The controller is also STOP'ed.
	 *
	 * Arghh!!! Why does the Intel PIIX3 and PIIX4 doc have this backwards?
	 */
	piix_cmd.byte = 0;
	piix_cmd.bits.rwcon = isRead ? 1 : 0;
	outb(piix_base + PIIX_BMICX, piix_cmd.byte);
	
	/*
	 * Clear interrupt and error bits in the Status Register.
	 */
	piix_status.byte = inb(piix_base + PIIX_BMISX);
	piix_status.bits.err = piix_status.bits.ideints = 1;
//	piix_status.bits.dma0cap = piix_status.bits.dma1cap = 1;
	outb(piix_base + PIIX_BMISX, piix_status.byte);
	
	return YES;
}

/*
 * Method: PIIXInit
 *
 * Purpose:
 * Initializes the PIIX controller.
 */
- (void) PIIXInit
{
	return (PIIXStopDMA(_bmRegs));
}

/*
 * Even if an interrupt is missed, consider the transfer operation
 * successful if the PIIX status flags says so.
 */
#define TRUST_PIIX	1

/*
 * Method: PIIXPerformDMA
 *
 * Purpose:
 * Program the PIIX controller to perform DMA READ/WRITE transfers
 * based on the transfer request ideIoReq. The entire transfer is
 * translated into one or more PRD entries in the PRD table. We will
 * get a single interrupt when the entire transfer is complete.
 *
 * Note:
 * The PIIX status register should have bit 2 and bit 0 set at the
 * conclusion of the transfer. This corresponds to the case when the
 * IDE device generated an interrupt and the size of the PRD is equal
 * to the IDE device transfer size.
 *
 * If bit 1 is set, meaning that the controller encountered a target
 * or master abort, it is very likely that we told it to DMA to/from
 * an invalid piece of memory. Perhaps due to an incorrect virtual
 * to physical map conversion.
 */
- (ide_return_t) performDMA:(ideIoReq_t *)ideIoReq
{
	ideRegsVal_t	*ideRegs = &(ideIoReq->regValues);
	ideRegsAddrs_t	*rp = &_ideRegsAddrs;
    unsigned char	status;
	piix_bmisx_u	piix_status;
	ide_return_t	rtn = IDER_SUCCESS;
	unsigned 		cmd = ideIoReq->cmd;

	ddm_ide_dma("DMA block:%d count:%d read:%d map:%d rp:%x\n",
		ideIoReq->block,
		ideIoReq->blkcnt,
		(ideIoReq->cmd == IDE_READ_DMA),
		ideIoReq->map,
		rp->data);

	if ((cmd != IDE_READ_DMA) && (cmd != IDE_WRITE_DMA)) {
		IOLog("%s: ideDmaRwCommon: unknown command %d\n", [self name], cmd);
		return IDER_REJECT;
	}

	/*
	 * wait for BSY = 0 and DRDY = 1
	 */
    rtn = [self waitForDeviceReady];
    if (rtn != IDER_SUCCESS) {
		IOLog("%s: drive not ready\n", [self name]);
		return (rtn);
    }

	/*
	 * Set up PRD descriptor table
	 */
	if (PIIXSetupPRDTable(_prdTable.ptr,
		PIIX_DT_BOUND/sizeof(piix_prd_t),
		(vm_offset_t)ideIoReq->addr,
		ideIoReq->blkcnt * IDE_SECTOR_SIZE,
		(struct vm_map *)ideIoReq->map) == NO) {
		return NO;
	}

	/*
	 * Prepare the PIIX controller for the current transfer.
	 */
	if (PIIXPrepareDMA(_bmRegs, _tablePhyAddr,
		(ideIoReq->cmd == IDE_READ_DMA)) == NO) {
		IOLog("%s: PIIXPrepareDMA error\n", [self name]);
		return IDER_CMD_ERROR;
	}
	
	/*
	 * Program the drive (task file).
	 * Recall that _driveNum must be set prior to calling logToPhys.
	 * This is already done in the method ideExecuteCmd which calls
	 * this method. testDMA also calls this method with _driveNum set.
	 */
	*ideRegs = [self logToPhys:ideIoReq->block numOfBlocks:ideIoReq->blkcnt];
    outb(rp->drHead,  ideRegs->drHead);
    outb(rp->sectNum, ideRegs->sectNum);
    outb(rp->sectCnt, ideRegs->sectCnt);
    outb(rp->cylLow,  ideRegs->cylLow);
    outb(rp->cylHigh, ideRegs->cylHigh);

	ddm_ide_dma(
		"DMA drHead:%02x sectNum:%02x sectCnt:%02x cylLow:%02x cylHigh:%02x\n",
		ideRegs->drHead,
		ideRegs->sectNum,
		ideRegs->sectCnt,
		ideRegs->cylLow,
		ideRegs->cylHigh);

	/*
	 * Issue DMA READ/WRITE command to drive.
	 */
//	[self enableInterrupts];
//	[self clearInterrupts];
    outb(rp->command, cmd);

	/*
	 * Start the PIIX bus master.
	 */
	PIIXStartDMA(_bmRegs);
	
	/* Wait for interrupt to signal the completion of the transfer.
	 */
    rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];	
	piix_status.byte = PIIXGetStatus(_bmRegs);
	PIIXStopDMA(_bmRegs);
	
#ifdef TRUST_PIIX
	if ((piix_status.byte & PIIX_STATUS_MASK) == PIIX_STATUS_OK) {

			if (rtn != IDER_SUCCESS) {
				/* Interrupt timed-out, but PIIX claims that transaction
				 * was completed without errors.
				 * This may require more testing. Always trust PIIX?
				 * First, read status from the drive.
				 */
				if ((rtn = [self waitForNotBusy]) != IDER_SUCCESS)
					return IDER_CMD_ERROR;
				status = inb(rp->status);
			}			 
#else
	if ((rtn == IDER_SUCCESS) && ((piix_status.byte & PIIX_STATUS_MASK) == 
		PIIX_STATUS_OK)) {
#endif TRUST_PIIX

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

	    	if (status & ERROR_CORRECTED) {
				IOLog("%s: Error during data transfer (corrected).\n", 
			    	[self name]);
		    }
	}
	else {
		[self getIdeRegisters:ideRegs Print:NULL];
		IOLog("%s: PIIX status:0x%02x error code:%d\n",
			[self name], piix_status.byte, rtn);
		rtn = IDER_CMD_ERROR;
	}

	ddm_ide_dma(
		"END drHead:%02x sectNum:%02x sectCnt:%02x cylLow:%02x cylHigh:%02x\n",
    	inb(rp->drHead),
    	inb(rp->sectNum),
    	inb(rp->sectCnt),
    	inb(rp->cylLow),
    	inb(rp->cylHigh));

	return (rtn);
}

#define MAX_BUSY_WAIT 				(1000*100)

/*
 * Perform DMA transfers for ATAPI devices.
 */
- (sc_status_t) performATAPIDMA:(atapiIoReq_t *)atapiIoReq
	buffer:(void *)buffer 
	client:(struct vm_map *)client
{
	piix_bmisx_u piix_status;
	unsigned char cmd = atapiIoReq->atapiCmd[0];
	ide_return_t	rtn;
    unsigned char	status;
	ideRegsAddrs_t	*rp = &_ideRegsAddrs;
	int	i;

	//IOLog("DMA transfer\n");

	atapiIoReq->bytesTransferred = 0;
	
	/*
	 * Set up PRD descriptor table
	 */
	if (PIIXSetupPRDTable(_prdTable.ptr,
		PIIX_DT_BOUND/sizeof(piix_prd_t),
		(vm_offset_t)buffer,
		atapiIoReq->maxTransfer,
		client) == NO)
		{
		atapiIoReq->scsiStatus = STAT_CHECK;
		return SR_IOST_CMDREJ;
	}

	if (PIIXPrepareDMA(_bmRegs, _tablePhyAddr, atapiIoReq->read) == NO) {
		IOLog("%s: PIIXPrepareDMA error\n", [self name]);
		atapiIoReq->scsiStatus = STAT_CHECK;
		return SR_IOST_CMDREJ;
	}

	/*
	 * Start the PIIX bus master.
	 */
	PIIXStartDMA(_bmRegs);

	if (atapiIoReq->timeout > IDE_INTR_TIMEOUT) {
		u_int current_timeout = [self interruptTimeOut];
		
		//IOLog("using SCSI timeout:%d\n", atapiIoReq->timeout);
		[self setInterruptTimeOut:atapiIoReq->timeout];
		rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];
		[self setInterruptTimeOut:current_timeout];
	}
	else {
		rtn = [self ideWaitForInterrupt:cmd ideStatus:&status];
	}

	piix_status.byte = PIIXGetStatus(_bmRegs);
	PIIXStopDMA(_bmRegs);

	/*
	 * This is stupid but the Chinon drive fires off an interrupt first
	 * and then updates the status register. It appears that any drive
	 * based on Western Digital chipset will do this. At any rate, this
	 * code is harmless and should be left here. 
	 */
	for (i = 0; i < MAX_BUSY_WAIT; i++)	{
		if (status & BUSY)
			IODelay(10);
		else
			break;
		status = inb(_ideRegsAddrs.status);	
	}

#ifdef TRUST_PIIX
	if ((piix_status.byte & PIIX_STATUS_MASK) == PIIX_STATUS_OK) {
		if (rtn != IDER_SUCCESS) {
			/* Interrupt timed-out, but PIIX claims that transaction
			 * was completed without errors.
			 * This may require more testing. Always trust PIIX?
			 * First, read status from the drive.
			 */
			if ((rtn = [self waitForNotBusy]) != IDER_SUCCESS) {
				IOLog("%s: FATAL: ATAPI Drive: %d Command %x failed.\n", 
					[self name], _driveNum, atapiIoReq->atapiCmd[0]);
				[self getIdeRegisters:NULL Print:"ATAPI DMA"];
				IOLog("%s: transfer size: %d\n",
					[self name], atapiIoReq->maxTransfer);
				[self atapiSoftReset:_driveNum];
				atapiIoReq->scsiStatus = STAT_CHECK;
				return SR_IOST_CHKSNV;
			}
			status = inb(rp->status);
		}
#else
	if ((rtn == IDER_SUCCESS) && ((piix_status.byte & PIIX_STATUS_MASK) == 
		PIIX_STATUS_OK)) {
#endif TRUST_PIIX

		if (status & ERROR) {
			atapiIoReq->scsiStatus = STAT_CHECK;
			return SR_IOST_CHKSNV;	
		}
	}
	else {
		[self getIdeRegisters:NULL Print:"ATAPI DMA"];
		IOLog("%s: PIIX status:0x%02x error code:%d\n",
			[self name], piix_status.byte, rtn);
		IOLog("%s: transfer size: %d\n", [self name], atapiIoReq->maxTransfer);
		[self atapiSoftReset:_driveNum];
		atapiIoReq->scsiStatus = STAT_CHECK;
		return SR_IOST_CHKSNV;
	}

	atapiIoReq->bytesTransferred = atapiIoReq->maxTransfer;
	atapiIoReq->scsiStatus = STAT_GOOD;
	return SR_IOST_GOOD;
}

@end

unix.superglobalmegacorp.com

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