Source to bsd/dev/ppc/drvApple96_SCSI/Apple96SCSI.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.18  Initial conversion from AMDPCSCSIDriver sources.
 *
 * Apple96SCSI.h - "Main" public methods for the 53C96 PCI SCSI interface.
 * Apple96SCSI is closely based on Doug Mitchell's AMD 53C974/79C974 driver.
 *
 * Hmm, these methods are probably common to all SCSI implementations.
 *
 * Edit History
 * 1998.07.29   MM	Disable logRegisters on command timeout.
 * 1997.02.13	MM		Initial conversion from AMDPCSCSIDriver sources. 
 * 1997.11.12	MM	Added support for scatter-gather lists.
 */
#import "Apple96SCSI.h"
#import "Apple96SCSIPrivate.h"
#import "Apple96CurioPrivate.h"
#import "Apple96CurioPublic.h"
#import "Apple96CurioDBDMA.h"
#import "Apple96Hardware.h"
#import "Apple96HWPrivate.h"
#import "Apple96ISR.h"
#import "bringup.h"
#import <mach/message.h>
#import <driverkit/generalFuncs.h>
#import <driverkit/interruptMsg.h>
#import <driverkit/align.h>
#import <driverkit/kernelDriver.h>
#import <kernserv/prototypes.h>
#import <driverkit/IOSimpleMemoryDescriptor.h>
/* string.h defines bzero */
#import <string.h>


@implementation Apple96_SCSI

/*
 * Create and initialize one instance of Apple96_SCSI. The work is done by
 * architecture- and chip-specific modules. 
 */
+ (Boolean)		probe	: deviceDescription
{
		Apple96_SCSI		*inst = [self alloc];
		Boolean				result;

		ENTRY("Spr probe");
		MakeTimestampRecord(512);		/* TBS: contitionally compile */
		/*
		 * Perform device-specific initialization -- free the instance
		 * on failure.
		 */
		if ([inst hardwareInitialization : deviceDescription] == nil) {
			result = NO;
		}
		else {
			result = YES;
		}
		RESULT(result);
		return (result);
}

- free
{
		CommandBuffer		cmdBuf;
		
		ENTRY("Sfr free");
		/*
		 * First kill the I/O thread if running. 
		 */
		if (gFlagIOThreadRunning) {
			cmdBuf.op = kCommandAbortRequest;
			cmdBuf.scsiReq = NULL;
			[self executeCmdBuf:&cmdBuf];
		}	
		if (gIncomingCommandLock != NULL) {
			[gIncomingCommandLock free];
		}
		[self dbdmaTerminate];	/* Free up DBDMA memory	*/
		EXIT();
		return [super free];
}


/*
 * Return required DMA alignment for current architecture.
 */
- (void)getDMAAlignment : (IODMAAlignment *)alignment;
{
    alignment->readStart   = DBDMA_ReadStartAlignment;
    alignment->writeStart  = DBDMA_WriteStartAlignment;
    alignment->readLength  = DBDMA_ReadLengthAlignment;
    alignment->writeLength = DBDMA_WriteLengthAlignment;
}

/*
 * Statistics support.
 */
- (unsigned int) numQueueSamples
{
    return (gTotalCommands);
}


- (unsigned int) sumQueueLengths
{
    return (gQueueLenTotal);
}


- (unsigned int) maxQueueLength
{
    return (gMaxQueueLen);
}


- (void)resetStats
{
    gTotalCommands = 0;
    gQueueLenTotal = 0;
    gMaxQueueLen   = 0;
}

/**
 * Do a SCSI command, as specified by an IOSCSIRequest. All the 
 * work is done by the I/O thread.
 * @param   scsiReq	The request to execute
 * @param   buffer	The data buffer to transfer to/from, if any
 * @param   client	The data buffer owner task (for VM munging) 
 *
 * This method is called from IOSCSIDevice
 */
- (sc_status_t) executeRequest
					: (IOSCSIRequest *) scsiReq
	    buffer  : (void *) buffer 
	    client  : (vm_task_t) client
{
	IOMemoryDescriptor  *mem = NULL;
	sc_status_t	scsiStatus = SR_IOST_GOOD;  /* Fool compiler */

	ENTRY("Ser executeRequest");
	/*
	 * Create a simple I/O memory descriptor for this client,
	 * then toss it to the common method.
	 */
	if (buffer != NULL) {
            mem = [[IOSimpleMemoryDescriptor alloc]
                        initWithAddress : (void *) buffer
                        length          : scsiReq->maxTransfer
                    ];
            [mem setClient : client];
	}
	scsiStatus = [self executeRequest
			: scsiReq
		ioMemoryDescriptor : mem
	    ];
	if (mem != NULL) {
	    [mem release];
	}
	RESULT(scsiReq->bytesTransferred);
	return (scsiStatus);
}

/*
 * Execute a SCSI request using an IOMemoryDescriptor. This allows callers to
 * provide (kernel-resident) logical scatter-gather lists. For compatibility
 * with existing implementations, the low-level SCSI device driver must first
 * ensure that executeRequest:ioMemoryDescriptor is supported by executing:
 *  [controller respondsToSelector : executeRequest:ioMemoryDescriptor]
 */
- (sc_status_t) executeRequest
		    : (IOSCSIRequest *) scsiReq 
	ioMemoryDescriptor  : (IOMemoryDescriptor *) ioMemoryDescriptor 
{
		CommandBuffer		commandBuffer;

	ENTRY("Sem executeRequest (IOMemoryDescriptor)");
		TAG("Ser", scsiReq->maxTransfer);
		ddmExported("executeRequest: cmdBuf 0x%x maxTransfer 0x%x\n", 
			&commandBuffer,
			scsiReq->maxTransfer,
			3,4,5
		);
	if (ioMemoryDescriptor != NULL) {
	    [ioMemoryDescriptor setMaxSegmentCount : kMaxDMATransfer];
	    [ioMemoryDescriptor state : &commandBuffer.savedDataState];
	}
		bzero(&commandBuffer, sizeof(CommandBuffer));
		commandBuffer.op			= kCommandExecute;
		commandBuffer.scsiReq		= scsiReq;
	commandBuffer.mem	= ioMemoryDescriptor;
	scsiReq->driverStatus	    = SR_IOST_INVALID;	/* "In progress"    */
		[self executeCmdBuf : &commandBuffer];
		ddmExported("executeRequest: cmdBuf 0x%x complete; driverStatus %s\n", 
			&commandBuffer,
			IOFindNameForValue(scsiReq->driverStatus, IOScStatusStrings),
			3,4,5
		);
		RESULT(commandBuffer.scsiReq->bytesTransferred);
#if TIMESTAMP_AT_IOCOMPLETE
		[self logTimestamp : "I/O complete"];	/* After RETURN macro */
#endif
		return (commandBuffer.scsiReq->driverStatus);
}

/**
 *  Reset the SCSI bus. All the work is done by the I/O thread.
 */
- (sc_status_t) resetSCSIBus
{
		CommandBuffer		commandBuffer;

		ENTRY("Sre resetSCSIBus");
		ddmExported("resetSCSIBus: cmdBuf 0x%x\n",
			&commandBuffer,
			2, 3,4,5
		);

		commandBuffer.op			= kCommandResetBus;
		commandBuffer.scsiReq		= NULL;

		[self executeCmdBuf:&commandBuffer];
		ddmExported("resetSCSIBus: cmdBuf 0x%x DONE\n",
			&commandBuffer,
			2, 3,4,5
		);
		RESULT(SR_IOST_GOOD);
	return (SR_IOST_GOOD);		/* can not fail */
}

/*
 * The following 6 methods are all called from the I/O thread in IODirectDevice. 
 */
 
/*
 * Called from the I/O thread when it receives an interrupt message.
 * Currently all work is done by chip-specific module; maybe we should 
 * put this method there....
 */
- (void) interruptOccurred
{
		ENTRY("Sin interruptOccurred");
		/*
		 * Note: the following compiles because ENTRY ends with "do {"
		 */
#if DDM_DEBUG
    {
		/*
		 * calculate interrupt service time if enabled.
		 */
		ns_time_t			startTime;
		unsigned			elapsedNSec = 0;

		if (IODDMMasks[APPLE96_DDM_INDEX] & DDM_INTR) {
			IOGetTimestamp(&startTime);
		}
		ddmInterrupt("interruptOccurred: TOP\n", 1,2,3,4,5);
#endif	DDM_DEBUG
		[self hardwareInterrupt];
#if DDM_DEBUG
		if (IODDMMasks[APPLE96_DDM_INDEX] & DDM_INTR) {
			ns_time_t	endTime;
			
			IOGetTimestamp(&endTime);
			elapsedNSec = (unsigned) (endTime - startTime);
		}
		ddmInterrupt("interruptOccurred: DONE; elapsed time %d.%03d us\n", 
			elapsedNSec / 1000U,
			elapsedNSec - ((elapsedNSec / 1000U) * 1000U),
			3,4,5
		);
    }
#endif	DDM_DEBUG
//		[self enableInterrupt:0];
//		[self enableAllInterrupts ];
		EXIT();
}

/*
 * These three should not occur; they are here as error traps. All three are 
 * called out from the I/O thread upon receipt of messages which it should
 * not be seeing.
 */
- (void)interruptOccurredAt:(int)localNum
{
		IOLog("%s: interruptOccurredAt:%d\n", [self name], localNum);
}

- (void)otherOccurred:(int)id
{
		IOLog("%s: otherOccurred:%d\n", [self name], id);
}

- (void)receiveMsg
{
		IOLog("%s: receiveMsg\n", [self name]);
		
		/*
		 * We have to let IODirectDevice take care of this (i.e., dequeue the
		 * bogus message).
		 */
		[super receiveMsg];
}

/*
 * Used in -timeoutOccurred to determine if specified cmdBuf has timed out.
 * Returns YES if timeout, else NO.
 */
static inline Boolean
isCmdTimedOut(
		CommandBuffer		*cmdBuf,
		ns_time_t			now
    )
{
		IOSCSIRequest		*scsiReq;
		ns_time_t			expire;
		Boolean				result;

		ENTRY("Sit isCmdTimedOut");
		scsiReq = cmdBuf->scsiReq;
	expire	= cmdBuf->startTime + 
			(1000000000ULL * (unsigned long long) scsiReq->timeoutLength);
		result = (now > expire);
#if 0
		if (result) {
			IOLog("%s: command timeout, target %d exceeded %d\n"
		"drvApple96SCSI",   /* Can't use [self name] here */
				(int) scsiReq->target,
				(int) scsiReq->timeoutLength
			);
		}
#endif
		RESULT(result);
		return (result);
}

/*
 * Called from the I/O thread when it receives a timeout
 * message. We send these messages ourself from serviceTimeoutInterrupt().
 */
- (void) timeoutOccurred
{
		ns_time_t			now;
		Boolean				cmdTimedOut = NO;
		CommandBuffer		*cmdBuf = gActiveCommand;
		CommandBuffer		*nextCmdBuf;

		ENTRY("Sto timeoutOccurred");
		ddmThread("timeoutOccurred: TOP\n", 1,2,3,4,5);
		IOGetTimestamp(&now);
#if 0 /* Disable. Part of Radar 2261237 */
		[self logRegisters : TRUE  reason : "TimeoutOccurred"];
#endif /* Disable. Part of Radar 2261237 */
// ** ** **
// ** ** ** Umm, is there a race-condition here? Can gActiveCommand be changed
// ** ** ** out from under us by an interrupt or does the O.S. ensure that
// ** ** ** these methods are single-threaded? Also, note that the bus
// ** ** ** is likely to be stuck if there is a very slow device
// ** ** ** such as a Format or Tape Rewind ongoing. This needs to be
// ** ** ** updated to use the Copland (and/or SCSI Manager 4.3) algorithms.
// ** ** **
		/*
		 *  Scan gActiveCommand and disconnectQ looking for tardy I/Os.
		 */
		if (cmdBuf != NULL) {
			if (isCmdTimedOut(cmdBuf, now)) {
				ddmThread("gActiveCommand TIMEOUT, cmd 0x%x\n", 
					cmdBuf, 2,3,4,5);
				gActiveCommand = NULL;
				/*
				 * ** ** ** Umm, what about abort commands?
				 */
				ASSERT(cmdBuf->scsiReq != NULL);
		[self ioComplete
			    : cmdBuf
		    finalStatus : SR_IOST_IOTO
		];
				cmdTimedOut = YES;
			}
		}
		/*
		 * ** ** **
		 * ** ** ** Umm, disconnected commands should timeout only
		 * ** ** ** when the bus is idle. Otherwise, we can timeout
		 * ** ** ** a command whose only fault is that other commands
		 * ** ** ** saturated the bus.
		 */
		cmdBuf = (CommandBuffer *) queue_first(&gDisconnectedCommandQueue);
		while (queue_end(&gDisconnectedCommandQueue, (queue_entry_t) cmdBuf) == 0) {
			if (isCmdTimedOut(cmdBuf, now)) {
				ddmThread("disconnected cmd timeout, cmd 0x%x\n",  cmdBuf, 2,3,4,5);
			
				/*
				 *  Remove cmdBuf from disconnectQ and complete it.
				 */
				nextCmdBuf = (CommandBuffer *) queue_next(&cmdBuf->link);
				queue_remove(&gDisconnectedCommandQueue, cmdBuf, CommandBuffer *, link);
				ASSERT(cmdBuf->scsiReq != NULL);
		[self ioComplete
			    : cmdBuf
		    finalStatus : SR_IOST_IOTO
		];
				cmdBuf = nextCmdBuf;
				cmdTimedOut = YES;
			}
			else {
				cmdBuf = (CommandBuffer *) queue_next(&cmdBuf->link);
			}
		}
		/*
		 * ** ** **
		 * ** ** ** This is a really big hammer -- and will cause problems
		 * ** ** ** with tape drives. It may also cause data loss.
		 * ** ** **
		 * Reset bus. This also completes all I/Os in disconnectQ with
		 * status CS_Reset.
		 */
		if (cmdTimedOut) {
			[self logRegisters : TRUE reason : "Command timeout"];
			[self threadResetBus:NULL];
		}
		ddmThread("timeoutOccurred: DONE\n", 1,2,3,4,5);
		EXIT();
}

/*
 * Process all commands in gIncomingCommandQueue. At most one of these will become
 * gActiveCommand. The remainder of kCommandExecute commands go to gPendingCommandQueue. Other
 * types of commands (such as bus reset) are executed immediately.
 *
 * This method is called from IODirectDevice.
 * ** ** **
 * ** ** ** Note that we don't have a concept of frozen queue.
 * ** ** **
 */
- (void) commandRequestOccurred
{
		CommandBuffer		*cmdBuf;
		CommandBuffer		*pendCmd;

		ENTRY("Sco commandRequestOccurred");
		ddmThread("commandRequestOccurred: top\n", 1,2,3,4,5);
		[gIncomingCommandLock lock];
		while (queue_empty(&gIncomingCommandQueue) == FALSE) {
			cmdBuf = (CommandBuffer *) queue_first(&gIncomingCommandQueue);
			queue_remove(&gIncomingCommandQueue, cmdBuf, CommandBuffer *, link);
			[gIncomingCommandLock unlock];
			switch (cmdBuf->op) {
			case kCommandResetBus:
			   	/* 
				 * Note all active and disconnected commands will
				 * be terminted.
				 */
				[self threadResetBus : "Reset Command Received"];
		[self ioComplete
			    : cmdBuf
		    finalStatus : SR_IOST_RESET
		];
				break;
			case kCommandAbortRequest:
				/*
				 * 1. Abort all active, pending, and disconnected
		 *  commands.
				 * 2. Notify caller of completion.
				 * 3. Self-terminate.
				 */
		[self abortAllCommands : SR_IOST_INT];
				pendCmd = (CommandBuffer *) queue_first(&gPendingCommandQueue);
				while (queue_end(&gPendingCommandQueue, (queue_entry_t) pendCmd) == FALSE) {
		    [self ioComplete
				: pendCmd
			finalStatus : SR_IOST_INT
		    ];
					pendCmd = (CommandBuffer *) queue_next(&pendCmd->link);
				}
				[cmdBuf->cmdLock lock];
				[cmdBuf->cmdLock unlockWith:CMD_COMPLETE];
				ddmEntry("-Method: commandRequestOccurred", 1, 2, 3, 4, 5);
				IOExitThread();
				/* not reached */
			case kCommandExecute:
				[self threadExecuteRequest:cmdBuf];
				break;
				
			}
			[gIncomingCommandLock lock];
		}
		[gIncomingCommandLock unlock];
		ddmThread("commandRequestOccurred: DONE\n", 1,2,3,4,5);
		EXIT();
}


/*
 * Power management methods. All we care about is power off, when we must 
 * reset the SCSI bus due to the Compaq BIOS's lack of a SCSI reset, which
 * causes a hang if we have set up targets for sync data transfer mode.
 */
- (IOReturn) getPowerState : (PMPowerState *) state_p
{
	 	ddmExported("getPowerState called\n", 1,2,3,4,5);
	   	return IO_R_UNSUPPORTED;
}

- (IOReturn) setPowerState : (PMPowerState) state
{
#ifdef DEBUG
		IOLog("%s: received setPowerState: with %x\n", [self name],
			(unsigned)state);
#endif DEBUG
		if (state == PM_OFF) {
			// [self scsiReset];
			// ** ** ** TBS: [self powerDown];
			return IO_R_SUCCESS;
		}
		return IO_R_UNSUPPORTED;
}

- (IOReturn) getPowerManagement : (PMPowerManagementState *) state_p
{
		ddmExported("getPowerManagement called\n", 1,2,3,4,5);
		return IO_R_UNSUPPORTED;
}

- (IOReturn) setPowerManagement : (PMPowerManagementState) state
{
		ddmExported("setPowerManagement called\n", 1,2,3,4,5);
		return IO_R_UNSUPPORTED;
}

#if APPLE96_ENABLE_GET_SET

/**
 * Called by clients (and user-interaction) to configure the device.
 */
- (IOReturn) setIntValues
						: (unsigned *)		parameterArray
	forParameter	: (IOParameterName) parameterName
	count		: (unsigned int)    count
{
		IOReturn		ioReturn = IO_R_INVALID_ARG;
		
		/* Radar 1670762 ENTRY("Ssi setIntValues"); */
		ddmExported("setIntValues %s, count %u\n",
				parameterName,
				count,
				3, 4, 5
			);
		if (strcmp(parameterName, APPLE96_AUTOSENSE) == 0) {
			if (count == 1) {
				gOptionAutoSenseEnable = (parameterArray[0] ? 1 : 0);
				IOLog("%s: autoSense %s\n", [self name], 
					(gOptionAutoSenseEnable ? "Enabled" : "Disabled"));
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_ENABLE_DISCONNECT) == 0) { /* Radar 2005639 */
			if (count == 1) {
				gOptionDisconnectEnable = (parameterArray[0] ? 1 : 0);
				IOLog("%s: disconnect %s\n", [self name], 
					(gOptionDisconnectEnable ? "Enabled" : "Disabled"));
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_CMD_QUEUE) == 0) {
			if (count == 1) {
				gOptionCmdQueueEnable = (parameterArray[0] ? 1 : 0);
				IOLog("%s: cmdQueue %s\n", [self name], 
					(gOptionCmdQueueEnable ? "Enabled" : "Disabled"));
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_SYNC) == 0) {
			if (count == 1) {
				gOptionSyncModeEnable = (parameterArray[0] ? 1 : 0);
				IOLog("%s: syncMode %s\n", [self name], 
					(gOptionSyncModeEnable ? "Enabled" : "Disabled"));
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_FAST_SCSI) == 0) {
			if (count == 1) {
				gOptionFastModeEnable = (parameterArray[0] ? 1 : 0);
				IOLog("%s: fastMode %s\n", [self name], 
					(gOptionFastModeEnable ? "Enabled" : "Disabled"));
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_RESET_TARGETS) == 0) {
			if (count == 0) {
				int					target;
			
				/*
				 * Re-enable sync and command queueing. The
				 * disable bits persist after a reset.
				 */
				for (target = 0; target < SCSI_NTARGETS; target++) {
					PerTargetData		*perTargetPtr;
		
					perTargetPtr = &gPerTargetData[target];
		    perTargetPtr->cmdQueueDisable   = FALSE;
		    perTargetPtr->selectATNDisable  = FALSE;
					perTargetPtr->maxQueue			= 0;
				}
				IOLog("%s: Per Target disable flags cleared\n", [self name]);
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_RESET_TIMESTAMP) == 0) {
			ResetTimestampIndex();
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_ENABLE_TIMESTAMP) == 0) {
			EnableTimestamp(TRUE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_DISABLE_TIMESTAMP) == 0) {
			EnableTimestamp(FALSE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_PRESERVE_FIRST_TIMESTAMP) == 0) {
			PreserveTimestamp(TRUE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_PRESERVE_LAST_TIMESTAMP) == 0) {
			PreserveTimestamp(FALSE);
			ioReturn = IO_R_SUCCESS;
		}
		else {
			ioReturn = [super setIntValues
									: parameterArray
		    forParameter    : parameterName
					count			: count
				];
		}
		/* Radar 1670762 RESULT(ioReturn); */
		return (ioReturn);
}

/**
 * Called by clients and user-interaction to retrieve information from
 * the device. Note that non-privileged clients can call getIntValues
 * and it may be used to *set* timestamp parameters.
 */
- (IOReturn) getIntValues
							: (unsigned *)		parameterArray
	forParameter	    : (IOParameterName) parameterName
		count				: (unsigned *)		count;		/* in/out	*/
{
		IOReturn			ioReturn = IO_R_INVALID_ARG;
		
		/* Radar 1670762 ENTRY("Sgi getIntValues"); */
		if (strcmp(parameterName, APPLE96_AUTOSENSE) == 0) {
			if (*count == 1) {
				parameterArray[0] = gOptionAutoSenseEnable;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_CMD_QUEUE) == 0) {
			if (*count == 1) {
				parameterArray[0] = gOptionCmdQueueEnable;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_SYNC) == 0) {
			if (*count == 1) {
				parameterArray[0] = gOptionSyncModeEnable;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_FAST_SCSI) == 0) {
			if (*count == 1) {
				parameterArray[0] = gOptionFastModeEnable;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_ENABLE_DISCONNECT) == 0) { /* Radar 2005639 */
			if (*count == 1) {
				parameterArray[0] = gOptionDisconnectEnable;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE_MAX_THREADS) == 0) { /* Radar 2005639 */
			if (*count == 1) {
				parameterArray[0] = MAX_CLIENT_THREADS;
				ioReturn = IO_R_SUCCESS;
			}
		}
		else if (strcmp(parameterName, APPLE96_RESET_TIMESTAMP) == 0) {
			ResetTimestampIndex();
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_ENABLE_TIMESTAMP) == 0) {
			EnableTimestamp(TRUE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_DISABLE_TIMESTAMP) == 0) {
			EnableTimestamp(FALSE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_PRESERVE_FIRST_TIMESTAMP) == 0) {
			PreserveTimestamp(TRUE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_PRESERVE_LAST_TIMESTAMP) == 0) {
			PreserveTimestamp(FALSE);
			ioReturn = IO_R_SUCCESS;
		}
		else if (strcmp(parameterName, APPLE96_STORE_TIMESTAMP) == 0) {
			/*
			 * Count
			 *		1: tag, <zero>, current time
			 *		2: tag, value, current time
			 *		4: tag, value, user's time
			 */
			switch (*count) {
			case 1:
				StoreTimestamp(parameterArray[0], 0);
				ioReturn = IO_R_SUCCESS;
				break;
			case 2:
				StoreTimestamp(parameterArray[0], parameterArray[1]);
				ioReturn = IO_R_SUCCESS;
				break;
			case 4:
				StoreNSecTimestamp(
					parameterArray[0],
					parameterArray[1],
					*((ns_time_t *) &parameterArray[2])
				);
				ioReturn = IO_R_SUCCESS;
				break;
			default:
				break;	/* Failure */
			}
		}
		else if (strcmp(parameterName, APPLE96_READ_TIMESTAMP) == 0) {
			/*
			 * Read the timestamp array:
			 *	parameterArray	-> result buffer (TimestampDataRecord vector)
			 *	count			-> maximum number of integers to return
			 *					<- set to the actual number of integers returned.
			 * Note that, because count is a number of integers, clients should
			 * call this as follows:
			 *		TimestampDataRecord		myTimestamps[123];
			 *		unsigned				count;
			 *		count = sizeof (myTimestamps) / sizeof (unsigned);
			 *		[scsiDevice getIntValues
			 *						: (unsigned int *) myTimestamps
			 *			forParameter	: APPLE96_READ_TIMESTAMP
			 *			count			: &count
			 *		];
			 *		count = (count * sizeof (unsigned)) / sizeof (TimestampDataRecord);
			 *		for (i = 0; i < count; i++) {
			 *			Process(myTimestamps[i]);
			 *		}
			 */
			unsigned	vectorSize = ((*count) * sizeof(unsigned))
								/ sizeof (TimestampDataRecord);
			ReadTimestampVector((TimestampDataPtr) parameterArray, &vectorSize);
			*count = (vectorSize * sizeof (TimestampDataRecord))
								/ sizeof (unsigned);
			ioReturn = IO_R_SUCCESS;
		}
		else {
			ioReturn = [super getIntValues : parameterArray
				forParameter : parameterName
				count : count];
		}
		/* RESULT(ioReturn); */
		return (ioReturn);
}					


#endif	/* APPLE96_ENABLE_GET_SET */

@end	/* Apple96_SCSI */