Source to bsd/dev/ppc/drvApple96_SCSI/Apple96CurioPublic.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 (c) 1994-1996 NeXT Software, Inc.  All rights reserved. 
 * Copyright 1997 Apple Computer Inc. All Rights Reserved.
 * @author	Martin Minow	mailto:[email protected]
 * @revision	1997.02.17	Initial conversion from AMDPCSCSIDriver sources.
 *
 * Set tabs every 4 characters.
 *
 * Apple96CurioPublic.m - Chip-specific methods for Apple96 SCSI driver.
 *
 * Edit History
 * 1997.02.18	MM		Initial conversion from AMDPCSCSIDriver sources. 
 */
#import "Apple96SCSI.h"
#import "Apple96CurioPublic.h"
#import "Apple96CurioPrivate.h"
#import "Apple96HWPrivate.h"
#import "Apple96CurioDBDMA.h"
#import "Apple96ISR.h"
#import <driverkit/generalFuncs.h>
#import <kernserv/prototypes.h>

@implementation Apple96_SCSI(CurioPublic)

/*
 * This should be extended to perform a real chip self-test.
 * (See the code in Copland DR1)
 */
- (IOReturn) hardwareChipSelfTest
{
		IOReturn			ioReturn = IO_R_SUCCESS;
		UInt8				chipID;
		
		ENTRY("Cst hardwareChipSelfTest");
		[self disableAllInterrupts];
	CURIOresetChip();
	CURIOwriteRegister(rCF2, CF2_ENFEATURES);
	CURIOsetCommandRegister(cNOP | bDMDEnBit);  /* DMA Nop	*/
	chipID = CURIOreadRegister(rTCH);
	CURIOresetChip();
		/*
		 * Allow the user to *disable* fast and sync if they
		 * were enabled by hardware probe, but do not allow the
		 * user to enable them if the hardware does not support
		 * the feature.
		 */
		switch (chipID) {
		case 0xA2:						/* NCR 53cf96		*/
		case 0x12:						/* AMD 53cf96		*/
			gOptionSyncModeSupportedByHardware = 1;
			gOptionFastModeSupportedByHardware = 1;
			break;
		default:						/* NCR 53c96		*/
			gOptionSyncModeSupportedByHardware = 0;
			gOptionFastModeSupportedByHardware = 0;
			break;
		}
		/*
		 * To do: write a test pattern into the fifo and check
		 * for correct count and bits.
		 */
		RESULT(ioReturn);
		return (ioReturn);
}

/*
 * Reusable hardware initializer function. if resetSCSIBus is TRUE, this
 * includes a SCSI reset. Handling of ioComplete of active and disconnected
 * commands must be done elsewhere. Returns IO_R_SUCCESS.
 * This is called with interrupts disabled.
 */
- (IOReturn) curioHardwareReset
						: (Boolean) resetSCSIBus
			reason		: (const char *) reason
{
		IOReturn			ioReturn = IO_R_SUCCESS;
		UInt8			 	configValue;
		UInt8				clockConversionFactor;
		UInt8				defaultSelectionTimeout;

		ENTRY("Chr curioHardwareReset");
	/*
	 * Temp for debugging
	 */
	scsiClockRate	    = kChipDefaultBusClockMHz;
		/*
		 * First of all, reset interrupts, the SCSI chip, and the DMA engine.
		 */
	CURIOresetChip();		/* Clear out the chip	    */
	DBDMAreset();			/* Stop the DMA engine	    */
	CURIOreadInterruptRegister();	/* Clear pending interrupt  */
		/*
		 * Initialize the chip.
		 */
	CURIOwriteRegister(rCF2, CF2_ENFEATURES);
	CURIOsetCommandRegister(cNOP | bDMDEnBit);  /* DMA Nop	*/

		/*
		 * Init state variables.
		 */
		gFlagCheckForAnotherInterrupt	= 0;
		gBusState				= SCS_DISCONNECTED;
		gFlagBusBusy					= FALSE;
		gCurrentBusPhase		= kBusPhaseBusFree;
		/*
		 * Smash all active command state (just in case).
		 */
		gActiveCommand			= NULL;
		gCurrentTarget			= kInvalidTarget;
		gCurrentLUN				= kInvalidLUN;
		gMsgInState				= kMsgInIdle;
		gMsgOutPtr = gMsgPutPtr	= gMsgOutBuffer;

		/*
		 * Configuration Register 1
		 *	Disable interrupt on initiator-instantiated bus reset (is this correct?)
		 *	Enable parity.
		 *	Set default bus ID (7) (This should be overriden by the Inspector)
		 */
		gInitiatorID = kDefaultInitiatorID;
		configValue = CF1_SRD | CF1_ENABPAR | gInitiatorID;
		if (gOptionExtendTiming) {
			/*
			 * Per instance table. This slows down transfers on the bus.
			 */
			configValue |= CF1_SLOW;
		}
		ddmInit("Configuration 1 = 0x%x\n", configValue, 2,3,4,5);
	CURIOwriteRegister(rCF1, configValue);
		/*
		 * Clock factor and select timeout.
		 */
		ASSERT(scsiClockRate != 0);
		/*
		 * Use the clock frequency (in MHz) to select the clock conversion
		 * factor. According to the NCR53CF94/96 data manual, the conversion
		 * factor is defined by the following table: Currently, we don't allow
		 * the caller to change selection timeout from the ANSI standard 250 Msec
		 * to avoid having to support floating-point register manipulation
		 */
		if (scsiClockRate < 10) {
			IOLog("Apple96_SCSI: Clock %d MHZ too low; using 10 MHz\n",
				 scsiClockRate);
			scsiClockRate = 10;
		}
		if (scsiClockRate > 40) {
			IOLog("Apple96_SCSI: Clock %d MHZ too high; using 40 MHz\n",
				 scsiClockRate);
			scsiClockRate = 40;
		}
		if		(scsiClockRate <= 10) {
			clockConversionFactor = ccf10MHz;
			defaultSelectionTimeout = SelTO16Mhz;
		}
		else if (scsiClockRate <= 15) {
			clockConversionFactor = ccf11to15MHz;
			defaultSelectionTimeout = SelTO16Mhz;
		}
		else if (scsiClockRate <= 20) {
			clockConversionFactor = ccf16to20MHz;
			defaultSelectionTimeout = SelTO16Mhz;
		}
		else if (scsiClockRate <= 25) {
			clockConversionFactor = ccf21to25MHz;
			defaultSelectionTimeout = SelTO25Mhz;
		}
		else if (scsiClockRate <= 30) {
			clockConversionFactor = ccf26to30MHz;
			defaultSelectionTimeout = SelTO33Mhz;
		}
		else if (scsiClockRate <= 35) {
			clockConversionFactor = ccf31to35MHz;
			defaultSelectionTimeout = SelTO40Mhz;
		}
		else {
			clockConversionFactor = ccf31to35MHz;
			defaultSelectionTimeout = SelTO40Mhz;
		}
		ddmInit("clockFactor %d\n", clockConversionFactor, 2,3,4,5);
	CURIOwriteRegister(rCKF, clockConversionFactor);
		ddmInit("select timeout reg 0x%x\n", defaultSelectionTimeout, 2,3,4,5);
	CURIOsetSelectionTimeout(defaultSelectionTimeout);

		/*
		 * Configuration Register 2 - enable extended features
		 *		- mainly, 24-bit transfer count.
		 */
	CURIOwriteRegister(rCF2, CF2_ENFEATURES);
		/*
		 * Configuration Register 3
		 */
		configValue = 0;
		if ((gOptionFastModeEnable && gOptionFastModeSupportedByHardware)
		 || scsiClockRate > 25) {
			configValue |= CF3_FASTSCSI;
		}
		ddmInit("control3 = 0x%x\n", configValue, 2,3,4,5);
	CURIOwriteRegister(rCF3, configValue);
		/*
		 * Configuration Register 4 - glitch eater, active negation.
		 * Let's not worry about these whizzy features just yet.
		 */
	CURIOwriteRegister(rCF4, 0);
		/*
		 * Go to async xfer mode for now. Sync gets enabled on a per-target 
		 * basis in -targetContext.
		 */
	CURIOsetSynchronousOffset(0);
		/*
		 * Reset SCSI bus, wait, clear possible interrupt.
		 */
		if (resetSCSIBus) {
			if (reason != NULL) {
				IOLog("%s: Resetting SCSI bus (%s)\n",
					[self name],
					reason
				);
			}
			else {
				IOLog("%s: Resetting SCSI bus\n", [self name]);
			}
	    CURIOresetSCSIBus();
			IOSleep(APPLE_SCSI_RESET_DELAY);
	    CURIOreadInterruptRegister();
			ddmInit("hardwareReset: enabling interrupts\n", 1,2,3,4,5);
		}
		ddmInit("hardwareReset: DONE\n", 1,2,3,4,5);
		RESULT(ioReturn);
		return (ioReturn);
}

/*
 * Terminate a DMA, including FIFO flush if necessary. Returns number of 
 * bytes transferred.
 */
- (UInt32) curioDMAComplete
{
	UInt32		fifoResidualCount;
		UInt32			actualTransferCount;
		UInt32			dmaResidualCount;
		UInt8			residualByte;
		
		ENTRY("Cdc curioDMAComplete");
	DBDMAstopTransfer();
		ASSERT(gActiveCommand != NULL && gActiveCommand->scsiReq != NULL);
		/*
		 * Get residual count from DMA transfer.
		 */
	SynchronizeIO();
	dmaResidualCount = CURIOgetTransferCount();
		actualTransferCount = gActiveCommand->thisTransferLength
						- dmaResidualCount;
		ddmChip("curioDMAComplete (dma): thisTransferLength %d, actualTransferCount %d\n",
			gActiveCommand->thisTransferLength, actualTransferCount, 3,4,5);
		if (actualTransferCount > gActiveCommand->thisTransferLength) {
			ddmError("fsmDMAComplete: DMA count exceeeded, %d expected, %d transferred\n",
				gActiveCommand->thisTransferLength,
				actualTransferCount,
				3, 4, 5
			);
		}
		/*
		 * Advance the current data index. Note that gActiveCommand->currentDataIndex
		 * will exceed scsiReq->maxTransfer if the target tries to transfer too much
		 * data. We need to add a bit-bucket handler here.
		 */
		gActiveCommand->currentDataIndex += actualTransferCount;
		[ gActiveCommand->mem setPosition : gActiveCommand->currentDataIndex ];
		/*
		 * Now, see if there are any bytes lurking in the fifo.
		 */
	SynchronizeIO();
	fifoResidualCount = CURIOgetFifoCount();
		if (fifoResidualCount != 0) {
			ddmChip("curioDMAComplete: fifoResidualCount %d\n",
				fifoResidualCount, 2, 3,4,5);
			if (gActiveCommand->scsiReq->read) {
				/*
				 * Try to move residual data from the fifo into the
				 * user's buffer.
				 */
/* ++ Radar xxxxxx use IOMemoryDescriptor */
		[ gActiveCommand->mem setPosition : gActiveCommand->currentDataIndex ];
		for (;;) {
		    SynchronizeIO();
		    if (CURIOgetFifoCount() == 0) {
			break;
				}
		    else {
			SynchronizeIO();
			residualByte = CURIOgetFifoByte();
			[gActiveCommand->mem writeToClient : &residualByte count : 1];
				gActiveCommand->currentDataIndex++;
			}
		}
			actualTransferCount += fifoResidualCount;						/* Radar 2207320	*/
/* -- Radar xxxxxx use IOMemoryDescriptor */
	    }
			else {
				CURIOflushFifo();
				CURIOclearTransferCountZeroBit();
				actualTransferCount					-= fifoResidualCount;	/* Radar 2207320	*/
				gActiveCommand->currentDataIndex	-= fifoResidualCount;
			}
			if (  gActiveCommand->mem )										/* Radar 2211821	*/
				[ gActiveCommand->mem setPosition : gActiveCommand->currentDataIndex ];
		}
		RESULT(actualTransferCount);
		return (actualTransferCount);
}

/*
 * Prepare for power down. 
 *  -- reset SCSI bus to get targets back to known state
 *  -- reset chip
 * Note: this presumes that a higher-power has cleaned out all
 * pending and in-progress commands.
 */
- (void) powerDown
{
		ENTRY("Cpd powerDown");
	DBDMAreset();			/* Stop the DMA engine	    */
	CURIOresetSCSIBus();		/* Reset the SCSI bus	    */
	IOSleep(APPLE_SCSI_RESET_DELAY);    /* Stall after reset	*/
	CURIOreadInterruptRegister();	/* Clear the interrupt	    */
	CURIOresetChip();		/* Clear out the chip	    */
	CURIOreadInterruptRegister();	/* Clear pending interrupt  */
		EXIT();
}

- (void) logRegisters
			: (Boolean) examineInterruptRegister
		reason	: (const char *) reason
{
#if 1 || DEBUG
	UInt8			currentStatus;
	UInt8			currentInterrupt;
	UInt8 			fifoDepth;
	UInt32			transferCount;
		
	ENTRY("Clo logRegisters");
	if (reason != NULL) {
	    IOLog("%s: *** Log registers: %s\n", [self name], reason);
	}
	IOLog("%s: *** last chip status 0x%02x, interrupt 0x%02x, command 0x%02x\n", 
		[self name],
		gSaveStatus,
		gSaveInterrupt,
		CURIOreadCommandRegister()
	    );
	IOLog("%s: *** bus state %s, bus phase %s, bus %s\n",
		[self name],
		IOFindNameForValue(gBusState, gAutomatonStateValues),
		IOFindNameForValue(gCurrentBusPhase, scsiPhaseValues),
		(gFlagBusBusy) ? "busy" : "free"
	    );
	currentStatus = CURIOreadStatusRegister();
	if (examineInterruptRegister) {
	    currentInterrupt = CURIOreadInterruptRegister();	/* Possible problem */
	    IOLog("%s: *** current status %02x current intrStatus %02x\n",
		    [self name],
		    currentStatus,
		    currentInterrupt
		);
	}
	else {
	    IOLog("%s: *** current status %02x\n",
		    [self name],
		    currentStatus
		);
	}
	IOLog("%s: *** syncOffset %d  syncPeriod 0x%02x\n",
		[self name],
		gLastSynchronousOffset,
		gLastSynchronousPeriod
	    );
	SynchronizeIO();
	fifoDepth	    = CURIOgetFifoCount();
	SynchronizeIO();
	transferCount	    = CURIOgetTransferCount();
	IOLog("%s: *** fifoDepth %d  transfer count %d\n",
		[self name],
		fifoDepth,
		transferCount
	    );
	if (gActiveCommand != NULL) {
	    [self logCommand
			    : gActiveCommand
		logMemory   : TRUE
		    reason  : "Active Command"
		];
	}
	[self logChannelCommandArea : reason];
	[self logIOMemoryDescriptor : reason];
	[self logTimestamp : reason];
	EXIT();
#endif	/* DEBUG */
}

- (void) logCommand
			: (const CommandBuffer *) commandPtr
	logMemory	: (Boolean) isLogMemoryNeeded
	reason		: (const char *) reason;
{
#if DEBUG
	ENTRY("Clc logCommand");
	if (reason == NULL) {
	    reason = "";
	}
	if (commandPtr == NULL) {
	    IOLog("%s: *** no command to log: %s.\n",
		    [self name],
		    reason
		);
	}
	else {
	    IOLog("%s: *** %s\n",
		    [self name],
		    reason
		);
	    IOLog("%s: *** %scommand at %08x, tag %02x, cdbLength %d, scsiReq %08x\n",
		    [self name],
		    (commandPtr == gActiveCommand) ? "active " : "",
		    (UInt32) commandPtr,
		    commandPtr->queueTag,
		    commandPtr->cdbLength,
		    (UInt32) commandPtr->scsiReq
		);
	    IOLog("%s: *** currentDataIndex %d, saved %d, thisTransfer %d\n",
		    [self name],
		    commandPtr->currentDataIndex,
		    commandPtr->savedDataIndex,
		    commandPtr->thisTransferLength
		);
	    IOLog("%s: *** active %d, is autosense %d, sense status %02x\n",
		    [self name],
		    commandPtr->flagActive,
		    commandPtr->flagIsAutosense,
		    commandPtr->autosenseStatus
		);
#if __IO_MEMORY_DESCRIPTOR_DEBUG__
	    if (commandPtr->mem != NULL) {
		[commandPtr->mem debugLogWithContainer : [self name]];
	    }
#endif
	    if (commandPtr->scsiReq != NULL) {
		IOSCSIRequest		*scsiReq = commandPtr->scsiReq;
#if (SERIOUS_DEBUGGING || DUMP_USER_BUFFER) && __IO_MEMORY_DESCRIPTOR_DEBUG__
		if (isLogMemoryNeeded
		 && scsiReq != NULL
		 && commandPtr->mem != NULL) {
		    IOMemoryDescriptorContext	context;
		    LogicalRange	    range;
		    ByteCount		newPosition = 0;

		    [cmdbuf->mem context : &context];
		    [cmdbuf->mem setOffset : 0];
		    while (newPosition < scsiReq->bytesTransferred
			&& [cmdbuf->mem getLogicalRange
					: 1
			maxByteCount	: scsiReq->bytesTransferred - newPosition
			newPosition	: &newPosition
			actualRanges	: NULL
			logicalRanges	: &range] != 0) {
			[self logMemory : range.address
				length  	: range.length
				reason  	: "User buffer at I/O Complete"
			    ];
			}
		    [cmdbuf->mem setContext : &context];
		}
#endif /* (SERIOUS_DEBUGGING || DUMP_USER_BUFFER) && __IO_MEMORY_DESCRIPTOR_DEBUG__ */
		IOLog("%s: *** %d.%d, cdb: "
			"%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
			[self name],
			scsiReq->target,
			scsiReq->lun,
			((UInt8 *) &scsiReq->cdb)[0],
			((UInt8 *) &scsiReq->cdb)[1],
			((UInt8 *) &scsiReq->cdb)[2],
			((UInt8 *) &scsiReq->cdb)[3],
			((UInt8 *) &scsiReq->cdb)[4],
			((UInt8 *) &scsiReq->cdb)[5],
			((UInt8 *) &scsiReq->cdb)[6],
			((UInt8 *) &scsiReq->cdb)[7],
			((UInt8 *) &scsiReq->cdb)[8],
			((UInt8 *) &scsiReq->cdb)[9],
			((UInt8 *) &scsiReq->cdb)[10],
			((UInt8 *) &scsiReq->cdb)[11]
		    );
		IOLog("%s: *** read %d, disconnect %d, maxTransfer %d, bytesTransferred %d\n",
			[self name],
			scsiReq->read,
			scsiReq->disconnect,
			scsiReq->maxTransfer,
			scsiReq->bytesTransferred
		    );
		IOLog("%s: *** driverStatus %d, scsiStatus %02x, sense: "
			"%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x.%02x\n",
			[self name],
			scsiReq->driverStatus,
			scsiReq->scsiStatus,
			((UInt8 *) &scsiReq->senseData)[0],
			((UInt8 *) &scsiReq->senseData)[1],
			((UInt8 *) &scsiReq->senseData)[2],
			((UInt8 *) &scsiReq->senseData)[3],
			((UInt8 *) &scsiReq->senseData)[4],
			((UInt8 *) &scsiReq->senseData)[5],
			((UInt8 *) &scsiReq->senseData)[6],
			((UInt8 *) &scsiReq->senseData)[7],
			((UInt8 *) &scsiReq->senseData)[8],
			((UInt8 *) &scsiReq->senseData)[9],
			((UInt8 *) &scsiReq->senseData)[10],
			((UInt8 *) &scsiReq->senseData)[11]
		    );
	    }
	}
	EXIT();
#endif /* DEBUG */
}

- (void) logTimestamp
		: (const char *) reason
{
#if DEBUG
/*
 * kMaxTimestamp should be greater than twice the expected method depth
 * since, if we dump the timestamp after it has wrapped around, we expect
 * to lose earlier entries and, hence, the shallower method starts.
 */
#ifndef kMaxTimestampStack
#define kMaxTimestampStack	64
#endif

	TimestampDataRecord	stack[kMaxTimestampStack + 1];	/* Allocate one extra */			
	UInt32			index = 0;
	int			start;
	UInt32			count;
	UInt32			maxDepth = 0;
	Boolean			wasEnabled;
	char			work[8];
	struct timeval		tv;
	ns_time_t		lastEventTime;
	UInt32			elapsed;
	UInt32			sinceMethodStart;
				
	/* ENTRY("Clt logTimestamp");	** No ENTRY so we don't corrupt timestamps */
	if (reason != NULL) {
	    IOLog("%s: *** Log timestamp: %s\n", [self name], reason);
	}
	/*
	 * In case something we call causes timestamping, we want to
	 * avoid getting into an infinite loop.
	 */
	wasEnabled = EnableTimestamp(FALSE);
	lastEventTime = stack[0].eventTime; /* Initialization hack */
	for (count = 1; ReadTimestamp(&stack[index]); count++) {
	    work[0] = (stack[index].timestampTag) >> 24 & 0xFF;
	    work[1] = (stack[index].timestampTag) >> 16 & 0xFF;
	    work[2] = (stack[index].timestampTag) >>  8 & 0xFF;
	    work[3] = (stack[index].timestampTag) >>  0 & 0xFF;
	    work[4] = '\0';
	    elapsed = (unsigned) stack[index].eventTime - lastEventTime;
	    lastEventTime = stack[index].eventTime;
	    ns_time_to_timeval(stack[index].eventTime, &tv);					    switch (work[0]) {
	    case '+':		/* Entering a method */
		IOLog("%s: '%s' %u.%06u %u.%03u 0.0 %d\n",
			[self name],
			work,
			tv.tv_sec,
			tv.tv_usec,
			elapsed / 1000,
			elapsed - ((elapsed / 1000) * 1000),
			(int) stack[index].timestampValue
		    );
		if (index < kMaxTimestampStack) {
		    ++index;
		    if (index > maxDepth) {
			maxDepth = index;
		    }
		}
		break;
	    case '=':	/* Intermediate tag: find the method start	*/
	    case '-':	/* End of method: find the method start		*/
		sinceMethodStart = 0;
		for (start = index - 1; start >= 0; --start) {
		    if ((stack[start].timestampTag & 0x00FFFFFF)
			    == (stack[index].timestampTag & 0x00FFFFFF)) {
			sinceMethodStart = (unsigned) stack[index].eventTime
					- stack[start].eventTime;
			break;
		    }
	        }
		IOLog("%s: '%s' %u.%06u %u.%03u %u.%03u %d\n",
			[self name],
			work,
			tv.tv_sec,
			tv.tv_usec,
			elapsed / 1000,
			elapsed - ((elapsed / 1000) * 1000),
			sinceMethodStart / 1000,
			sinceMethodStart - ((sinceMethodStart / 1000) * 1000),
			(int) stack[index].timestampValue
		    );
		if (start >= 0 && work[0] == '-') {
		    index = start;		/* Pop the stack */
		}
		break;
	    case '>':	/* Tag: log the value in hex			*/
	    case '<':	/* Tag: log the value in hex			*/
	    case '*':	/* Tag: log the value in hex			*/
		IOLog("%s: '%s' %u.%06u %u.%03u 0.0 %08x\n",
			[self name],
			work,
			tv.tv_sec,
			tv.tv_usec,
			elapsed / 1000,
			elapsed - ((elapsed / 1000) * 1000),
			stack[index].timestampValue
		    );
		/* The stack does not change */
		break;
	    default:
		IOLog("%s: '%s' %u.%06u %u.%03u 0.0 %08x\n",
			[self name],
			work,
			tv.tv_sec,
			tv.tv_usec,
			elapsed / 1000,
			elapsed - ((elapsed / 1000) * 1000),
			stack[index].timestampValue
		    );
		    break;
	    }
	}
	IOLog("%s: *** %d timestamps, %d max method depth\n",
		[self name],
		count,
		maxDepth
	    );
	EnableTimestamp(wasEnabled);
	/* EXIT(); ** No EXIT to avoid corrupting timestamps */
#endif /* DEBUG */
}

- (void) logMemory
			: (const void *) buffer
	length		: (UInt32) length
	reason		: (const char *) reason
{
#ifndef kMaxLogMemoryLength
#define kMaxLogMemoryLength	512
#endif
#if DEBUG
	const UInt8		*ptr;
	UInt32			start;
	UInt32			i;
	char			work[32];

	ENTRY("Clm logMemory");
	if (reason == NULL) {
	    reason = "";
	}
	IOLog("%s: *** %s: log memory from %08x for %d bytes\n",
		[self name],
		reason,
		(UInt32) buffer,
		length
	    );
	if (buffer != NULL) {
	    if (length > kMaxLogMemoryLength) {
		length = kMaxLogMemoryLength;
		IOLog("%s: *** Logging first %d bytes\n", [self name], length);
	    }
	    ptr = (const UInt8 *) buffer;
	    for (start = 0; start < length; start += 16) {
		IOLog("%s: *** %08x %3d:",
			[self name],
			(UInt32) &ptr[start],
			start
		    );
		for (i = 0; i < 16; i++) {
		    if ((i % 4) == 0) {
			IOLog(" ");
		    }
		    if (start + i >= length) {
			IOLog("  ");
			work[i] = '\0';
		    }
		    else {
			IOLog("%02x", ptr[start + i]);
			work[i] = (ptr[start + i] >= ' ' && ptr[start + i] <= '~')
				? ptr[start + i] : '.';
		    }
		}
		work[i] = '\0';
		IOLog(" %s\n", work);
	    }
	}
	EXIT();
#endif /* DEBUG */
}

@end	/* Apple96_SCSI(CurioPublic) */

/* end of Apple96_Chip.m */