//
//	SCSI2_Class - provides architecture independendent low-level
//				  SCSI device access.
//
//	Copyrigzht (C) 1994 by Christopher Wolf.
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Library General Public
//	License as published by the Free Software Foundation; either
//	version 2 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Library General Public License for more details.
//
//	You should have received a copy of the GNU Library General Public
//	License along with this library; if not, write to the Free
//	Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//	Bug-reports, comments and questions should be directed to:
//		Christopher Wolf <chris@alchemy.geo.cornell.edu>
//
//	Portions of this code and documentation are derived from an 
//	earlier work (SCSI Inquirer) Copyright (C) 1990, 1991, 1992 by 
//	Jiro Nakamura and used with permission.
//

//
// 	For best results view this file with a tab size of 4 and
//	a display width of 132 columns or wider.
//

//	file version information
#define RCSSM "$Id: SCSI.m,v 0.76 94/09/21 14:16:46 chris Exp Locker: chris $"

//	generic Unix headers
#import <libc.h>
#import <stdio.h>
#import <fcntl.h>
#import <stdio.h>
#import <objc/typedstream.h>

//	NeXTSTEP headers
#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>

//	SCSI2 class specific headers 
#import "SCSI.h"
#import "VersionNumber.h"
#import "scsi2reg.h"

// string constants
const char RCSsm[]  = RCSSM;
const char RCSsh[]  = RCSSH;
const char RCSsrh[] = RCSSRH;



//	**********************************************************************
//
//	Functions for reading and writing big endian values of
//	various bit sizes on both big (Motorola) and little (Intel) endian
//	architecture machines.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianLongFrom(void *address) 
//
//	PARAMETERS
//		void *address			pointer to MSB of 32 bit big-endian int
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads a long-word (4 bytes) stored in big-endian (MSB first) 
//		order starting from the specified address and returns the value
//		as a 32 bit int in native byte order.
//
//	SEE ALSO
//		readBigEndianShort(), readBigEndianValue(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue():
//
//	----------------------------------------------------------------------
	
int readBigEndianLong(void *address)
	{
	return readBigEndianValue(address,32);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianShortFrom(void *address) 
//
//	PARAMETERS
//		void *address			pointer to MSB of 16 bit big-endian int
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads a short-word (2 bytes) stored in big-endian (MSB first) 
//		order starting from the specified address and returns the value
//		as a 32 bit int in native byte order.  
//
//	SEE ALSO
//		readBigEndianLong(), readBigEndianValue(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue()
//
//	----------------------------------------------------------------------
	
int readBigEndianShort(void *address)
	{
	return readBigEndianValue(address, 16);	
	}


//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianValue(void *address, int numBits) 
//
//	PARAMETERS
//		void *address			pointer to MSB of big-endian int
//		int numBits				number of bits in big-endian int (1-32)
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads an integer consisting of the specified number of bits
//		stored in big-endian (MSB first) format starting from the
//		specified address.  The number of bits can be from 1-32.
//		The value is returned as a 32 bit int in native byte order.
//
//	SEE ALSO
//		readBigEndianShort(), readBigEndianLong(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue():
//
//	----------------------------------------------------------------------

int readBigEndianValue(void *address, int numBits) 
	{
	#ifdef __BIG_ENDIAN__
		return (*((int *)address))>>(32-numBits);
	#else
		return (htonl(*((int *)address)))>>(32-numBits);		
	#endif
	}


//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianLong(int value, void *address)
//
//	PARAMETERS
//		int value 				32 bit int value in native byte order
//		void *address			pointer to MSB of 32-bit destination
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a 32 bit big-endian value at the specified destination 
//		address.
//
//	SEE ALSO
//		writeBigEndianShort(), writeBigEndianValue(),
//		readBigEndianShort(), readBigEndianLong(), 
//		readBigEndianValue()
//
//	----------------------------------------------------------------------
	
void writeBigEndianLong(int value,  void *address)
	{
	writeBigEndianValue(value, address, 32);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianShort(int value, void *address) 
//
//	PARAMETERS
//		int value 			32 bit int value in native byte order
//		void *address		pointer to MSB of 16-bit destination
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a 16 bit big-endian value at the specified destination 
//		address.
//
//	SEE ALSO
//		writeBigEndianLong(), writeBigEndianValue(),
//		readBigEndianShort(), readBigEndianLong(), 
//		readBigEndianValue():
//
//	----------------------------------------------------------------------
	
void writeBigEndianShort(int value, void *address)
	{
	writeBigEndianValue(value, address, 16);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianValue(int value, void *address, int numbits) 
//
//	PARAMETERS
//		int *value 			32 bit int value in native byte order
//		void *address		pointer to MSB of destination
//		int numBits			number of bits for destination value
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a big-endian value with the specified number of bits
//		at the specified destination address.  Number of bits specified
//		can be between 1 and 32.
//
//	----------------------------------------------------------------------
	
void writeBigEndianValue(int value, void *address, int numBits)
	{
	#ifdef __BIG_ENDIAN
		*((int *)address) &= (~(0xFFFF<<(32-numBits)));
		*((int *)address) |= (value<<(32-numBits));	
	#else
		*((int *)address) &= htonl(~(0xFFFF<<(32-numBits)));
		*((int *)address) |= htonl(value<<(32-numBits));
	#endif
	}



@implementation SCSI



//	**********************************************************************
//	
//	Misc. Class (Factory) Methods
//	
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		new
//
//	RETURNS
//		self					pointer to new instance of SCSI class
//		
//	INSTANCE VARIABLES EFFECTED
//		isOpen					FALSE
//
//	EXPLANATION
//		Generates a new instance of the SCSI class.
//		
//	----------------------------------------------------------------------

+new
	{		
	self = [super new];
	target = -1;
	lun = 0;
 	dev_name = "/dev/sg";	
	isOpen = FALSE;
	return self;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		clearCommandBlock:
//
//	PARAMETERS
//		(union cdb *) cdb		pointer to command data block to zero
//
//	EXPLANATION
//		Clears the specifed command data block in preparation for issuing
//		a SCSI command.
//
//	----------------------------------------------------------------------

+ (void) clearCommandBlock: (union cdb *) cdb
	{	
	bzero((char *)cdb,sizeof(union cdb));
	return;
	}



//	**********************************************************************
//
//	Instance methods for archiving.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		read
//
//	RETURNS
//		self
//
//	EXPLANATION
//		Retrieves instance variables associated with this class during
//		an archive read: operation.
//
//	SEE ALSO
//		write:
//	
//	----------------------------------------------------------------------

- read: (NXTypedStream *) stream
	{
	[super read: stream];
	NXReadTypes( stream, "ii**", &target, &lun, &dev_name, &errorString);
	return self;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		write
//
//	RETURNS
//		self
//
//	EXPLANATION
//		Preserves instance variables associated with this class during
//		an archive write: operation.
//
//	SEE ALSO
//		read:
//	
//	----------------------------------------------------------------------

- write: (NXTypedStream *) stream
	{
	[super write: stream];
	NXWriteTypes( stream, "ii**", &target, &lun, &dev_name, &errorString);
	return self;
	}


//	**********************************************************************
//
//	Instance methods for opening, closing, and setting the target/lun
//	for the SCSI device.
//
//	**********************************************************************



//	----------------------------------------------------------------------
//
//	NAME
//		openTarget: lun: 
//
//	PARAMETERS
//		(int) trg		target to select after opening device	 
//		(int) lun		lun to select after opening device
//
//	RETURNS
//		(int) 0			0 = success, -1 failure
//
//	EXPLANATION
//		Opens the SCSI generic driver and selects the designated
//		target device and LUN.  The SCSI driver should be kept
//		open for as short a time as possible. Pre-packaged command
//		methods implemented in this class automatically open and close
// 		the SCSI driver, if necessary.  (However, if you are going
//		to rely on the auto open/close feature of pre-packaged commands
//		you must use setTarget:lun: to specify the target before issuing
//		the pre-packaged command.)
//
//	SEE ALSO:
//		open, close, setTarget: lun:
//		
//	----------------------------------------------------------------------

- (int) openTarget: (int) trg lun: (int) ln
	{
	
	int result;
	
	if (!(result = [self setTarget: trg lun: ln]))
		{
		result = [self open];
		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		open
//
//	RETURNS
//		(int) 			0 = success, -1 = failure
//
//	INSTANCE VARIABLES EFFECTED
//		isOpen			TRUE if open succeeded, FALSE if failed
//		errorString 	ASCII status message
//
//	EXPLANATION
//		Opens the SCSI generic driver.  The SCSI driver should be kept
//		open for as short a time as possible.  Pre-packaged command 
//		methods implemented in this class automatically	open and close
// 		the SCSI driver.  It is an error to attempt to open the SCSI 
//		device if it is already open.  Remember that you must call
//		setTarget:lun: either before or after the open method, but
//		definitely BEFORE any SCSI commands are issued.
//
//	SEE ALSO
//		openAt: lun:, close, isOpen
//	
//	----------------------------------------------------------------------

- (int) open
	{
	
	int result = -1;
	
	char tmpBuf[20];
	unsigned int tmp;
	
	if (isOpen == TRUE)
		{
		
		sprintf(errorString, "SCSI device already open!");

		}
	else
		{
		
		for(tmp = 0; ; tmp ++)
			{

			sprintf(tmpBuf, "%s%1X", dev_name, tmp)	;
	
			if (access(tmpBuf, F_OK))	
				{
				break;
				}
						
			if( (!access(tmpBuf, R_OK | W_OK))
				&& ((fd = open(tmpBuf, O_RDWR)) >= 0) ) 
				{
				isOpen = TRUE;
				result = 0;
				}
				
			}
			
		if (result)
			{
			sprintf(errorString,
					"Could not open SCSI generic driver <%sX>.\n"
					"File descriptor = 0x%X\n",
					tmpBuf, (unsigned int) fd);
			perror("open");
			}
		else
			{
			if (result = [self setTarget: target lun: lun])
				{
				[self close];
				}
			}

		}

	return result;
	
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		setTarget: lun: 
//
//	PARAMETERS
//		(int) trg		target to select 	 
//		(int) lun		lun to select 
//
//	RETURNS
//		(int) 			0 = Success, -1 = Error
//
//	SETS
//		isOpen			TRUE if open succeeded, FALSE if failed
//		errorString 	ASCII status message
//		target			selected SCSI target
//		lun				selected SCSI lun
//
//	EXPLANATION
//		Selects the indicated target and lun for subsequent SCSI commands.
//
//	SEE ALSO
//		openTarget: lun:
//		
//	----------------------------------------------------------------------

- (int) setTarget: (int) trg lun: (int) ln
	{
	
	int result = 0;
	
	if ( (trg < 0) || (trg > 7) || (lun < 0) || (lun > 7) )
		{
		
		sprintf(errorString,
				"Missing or invalid target (%d) or lun (%d) specification!\n", target,lun);
		result = -1;

		}		
	else
		{
		
		sa.sa_target = target = trg;
		sa.sa_lun = lun = ln;
		
		if (isOpen)
			{
	
			if (ioctl(fd,SGIOCSTL,&sa) < 0) 
				{
				sprintf(errorString,
						"ioctl(SGIOCSTL): Error setting target %d lun %d (errno = %d)\n", 
						target, lun, errno);
				result = -1;
				}
	
			}

		}
				
	return result;

	}

	
//	----------------------------------------------------------------------
//
//	NAME
//		close
//
//	RETURNS
//		(int) 0			Close succeeded
//		(int) -1		Error (ASCII error message in errorString)
//
//	INSTANCE VARIABLES EFFECTED
//		isOpen			TRUE if open succeeded, FALSE if failed
//		errorString 	ASCII status message
//
//	EXPLANATION
//		Closes the SCSI generic driver.  The SCSI driver should be kept
//		open for as short a time as possible.  Pre-packaged commands 
//		automatically open and close the SCSI driver.  It is an error
//		to close the SCSI device if it is not open.
//
//	SEE ALSO
//	    openAt: lun:, isOpen
//	
//	----------------------------------------------------------------------

- (int) close
	{
		
	if (isOpen == FALSE)
		{
		sprintf(errorString, "SCSI device already closed!");
		perror("close");
		return -1;
		}
		
	if (close(fd) < 0)  
		{
		sprintf(errorString, 	
				"Could not close %sX - fd = %XH\nerrno = %d\n", 
				dev_name, (unsigned int)fd ,errno);
		perror("close");
		return -1;
		}
		
	isOpen = FALSE;
	return 0;
		
	}



//	**********************************************************************
//
//	Instance methods for reporting status and error information.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		isOpen
//
//	RETURNS
//		(BOOL)			YES = Device Open, NO = Device Closed
//
//	EXPLANATION
//		This returns YES if the SCSI device is open and accessible,
//		NO otherwise.
//
//	SEE ALSO
//		open, close, openAt: lun:
//	
//	----------------------------------------------------------------------

- (BOOL) isOpen
	{
	return isOpen;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		errorString
//
//	RETURNS
//		(char *)			Pointer to ASCII error string.
//
//	EXPLANATION
//		This returns the class variable errorString which holds 
//		information (in ASCII format) about the last error. It is the 
//		responsibility of subclasses of SCSI to put the proper 
//		information in errorString when an error occurs. The SCSI 
//		class will not return errors in errorString for you. To access
//		bus level errors use the sr return codes or use returnError 
//		to interpret the sr return codes for you.  
//
//	SEE ALSO
//		statusReq
//	
//	----------------------------------------------------------------------

- (char *) errorString
	{
	return &errorString[0];
	}


//	----------------------------------------------------------------------
//
//	NAME
//		statusReq
//
//	RETURNS
//		(struct scsi_req *)			Pointer to ASCII error string.
//
//	EXPLANATION
//		This returns the status of the last SCSI request as a 
//		pointer to a structure defined in <nextdev/scsireg.h>.
//
//	SEE ALSO
//		errorString
//	
//	----------------------------------------------------------------------

- (struct scsi_req *) statusReq
	{
	return &sr;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		returnDriverStatus: scsiStatus: andExtendedSense:
//
//	PARAMETERS
//		(char **)driverStatus		pointer to pointer to char which
//									will be set to point to ASCII string 
//									containing driver status information.
//		(char **)scsiStatus			pointer to pointer to char which 
//									will be set to point to ASCII string
//									containing SCSI status information.
//		(char **)andExtendedSense 	pointer to pointer to char which
//									will be set to point to ASCII string
//									containing extended send information.
//
//	RETURNS
//		(char *)		Pointer to composite string containing all the
//						info in the three individual strings
//						driverStatus, scsiStatus and andExtendedSense,
//						concatenated into one long message.
//
//	EXPLANATION
//		This method places interpreted versions of the sr error codes, 
//		including the extended sense error codes if valid, into the 
//		character strings that were given to it. The format of the 
//		returned strings is not strict and should be only used for 
//		debugging purposes.  The actual error codes are more or less
//		taken from <nextdev/scsireg.h>. This method is provided as an
//		easy way to provide debugging information.
//
//	SEE ALSO
//		returnError
//		
//	----------------------------------------------------------------------

- (char *) returnDriverStatus: (char **) driverStatus scsiStatus: (char **) scsiStatus 
andExtendedSense: (char **) esense
	{

	static char stringBuffer[512];
	
	#ifdef DEBUG
		fprintf(stderr,"Io_Status = %02Xh, Status = %02Xh.\n",sr.sr_io_status,sr.sr_scsi_status);
	#endif
		
	switch(sr.sr_io_status)
		{
		case SR_IOST_GOOD:
			{
			*driverStatus = "Command successful";
			break;
			}
		case SR_IOST_SELTO:
			{
			*driverStatus = "Selection timeout";
			break;
			}
		case SR_IOST_CHKSV:
			{
			*driverStatus = "Check target status and extended sense";
			break;
			}
		case SR_IOST_CHKSNV:
			{
			*driverStatus = "Check target status";
			break;
			}
		case SR_IOST_DMAOR:
			{
			*driverStatus = "Target attempted to move more than allocated amount of data bytes";
			break;
			}
		case SR_IOST_IOTO:
			{
			*driverStatus = "Command timed out";
			break;
			}
		case SR_IOST_BV:
			{
			*driverStatus = "SCSI Bus violation";
			break;
			}
		case SR_IOST_CMDREJ:
			{
			*driverStatus = "Command rejected by driver";
			break;
			}
		case SR_IOST_MEMALL:
			{
			*driverStatus = "Memory allocation failure";
			break;
			}
		case SR_IOST_MEMF:
			{
			*driverStatus = "Memory fault";
			break;
			}
		case SR_IOST_PERM:
			{
			*driverStatus = "Permission failure (not super user)";
			break;
			}
		case SR_IOST_NOPEN:
			{
			*driverStatus = "Device not open";
			break;
			}
		case SR_IOST_TABT:
			{
			*driverStatus = "Target aborted command";
			break;
			}
		case ST_IOST_BADST:
			{
			*driverStatus = "Bad SCSI status byte (other than check status)";
			break;
			}
		case ST_IOST_INT:
			{
			*driverStatus = "Internal driver error";
			break;
			}
		case SR_IOST_BCOUNT:
			{
			*driverStatus = "Unexpected byte count seen on SCSI bus";
			break;
			}
		case SR_IOST_VOLNA:
			{
			*driverStatus = "Desired volume not available";
			break;
			}
		case SR_IOST_WP:
			{
			*driverStatus = "Media Write Protected";
			break;
			}
		default:
			{
			*driverStatus = "Unrecognizable status";
			break;
			}
		}
	
	switch(sr.sr_scsi_status & STAT_VALIDMASK)
		{	
		case STAT_GOOD:
			{
			if(sr.sr_io_status == SR_IOST_GOOD)
				{
				*scsiStatus = "GOOD - Command successful";
				}
			else
				{
				*scsiStatus = "Not Applicable (Command not executed)";
				}
			break;
			}
		case STAT_CHECK:
			{
			*scsiStatus = "CHECK CONDITION - Abnormal condition occured";
			break;
			}
		case STAT_CONDMET:
			{
			*scsiStatus = "CONDITION MET / GOOD";
			break;
			}
		case STAT_BUSY:
			{
			*scsiStatus = "BUSY - Target Busy";
			break;
			}
		case STAT_INTMGOOD:
			{
			*scsiStatus = "INTERMEDIATE / GOOD";
			break;
			}
		case STAT_INTMCONDMET:
			{
			*scsiStatus = "INTERMEDIATE / CONDITION MET / GOOD";
			break;
			}
		case STAT_RESERVED:
			{
			*scsiStatus = "RESERVATION CONFLICT";
			break;
			}
		case STAT_TERMINATED:
			{
			*scsiStatus = "COMMAND TERMINATED";
			break;
			}
		case STAT_QUEUEFULL:
			{
			*scsiStatus = "QUEUE FULL";
			break;
			}
		default:
			{
			*scsiStatus = "Unrecognizable status";
			}
		}
		
	switch( sr.sr_esense.er_sensekey )
		{
		case SENSE_NOSENSE:
			{
			*esense = "No error to report";
			break;
			}
		case SENSE_RECOVERED:
			{
			*esense = "Recovered from error";
			break;
			}
		case SENSE_NOTREADY:
			{
			*esense = "Target not ready";
			break;
			}
		case SENSE_MEDIA:
			{
			*esense = "Media flaw";
			break;
			}
		case SENSE_HARDWARE:
			{
			*esense = "Hardware failure";
			break;
			}
		case SENSE_ILLEGALREQUEST:
			{
			*esense = "Illegal request";
			break;
			}
		case SENSE_UNITATTENTION:
			{
			*esense = "Drive attention";
			break;
			}
		case SENSE_DATAPROTECT:
			{
			*esense = "Drive access protected";
			break;
			}
		case SENSE_ABORTEDCOMMAND:
			{
			*esense = "Target aborted command";
			break;
			}
		case SENSE_VOLUMEOVERFLOW:
			{
			*esense = "End of media, some data not transfered";
			break;
			}
		case SENSE_MISCOMPARE:
			{
			*esense = "Source/media data mismatch";
			break;
			}
		default:
			{
			*esense = "Unrecognizable sense key";
			break;
			}
		}
		
	if (SR_IOST_CHKSV == sr.sr_io_status)
		{
		sprintf(stringBuffer, 
				"\tDriver Status: \t%s\n"
				"\tTarget Status: \t%s\n"
				"\tExtended Sense: \t%s\n",
				*driverStatus,
				*scsiStatus,
				*esense);
		}
	else
		{
		sprintf(stringBuffer, 
				"\tDriver Status: \t%s\n"
				"\tTarget Status:\t%s\n",
				*driverStatus,
				*scsiStatus);
				*esense = NULL;
		}
	
	#ifdef DEBUG
		fprintf(stderr, "SCSI Completion string buf = %s\n", stringBuffer);
	#endif 
	
	return stringBuffer;

	}	


//	----------------------------------------------------------------------
//
//	NAME
//		returnError
//
//	RETURNS
//		(char *)		Pointer to a composite string containing
//						concatenated driverStatus, scsiStatus and
//						andExtendedSense info in one place.
//
//	EXPLANATION
//		This method is just a wrapper for the above defined method
//		returnDriverStatus: scsiStatus: andExtendedSense method
//		for cases where you are only interested in the concatenated
//		error string and don't want it broken down into individual
//		components.
//
//	SEE ALSO
//		returnDriverStatus: scsiStatus: andExtendedSense:
//		
//	----------------------------------------------------------------------

- (char *) returnError: sender
	{

	char *driverStatus, *scsiStatus, *esense;
	
	return [self returnDriverStatus: &driverStatus scsiStatus: &scsiStatus andExtendedSense: &esense];

	}




//	**********************************************************************
//
//	Instance methods for determining which 
//	devices are supported by a sub-class of SCSI class.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		findDevice:
//
//	PARAMETERS
//		(int) trg		Target to start search from 	 
//
//	RETURNS
//		(int) 			Target number of first SCSI device greater than
//						or equal to the specified starting number that
//						the sub-class can handle.  Returns -1 if no
//						appropriate device was able to be found.
//
//	EXPLANATION
//		This method starts at the specified target and sequentially steps
//		through the rest of the targets calling the isDevice: method for 
//		each target.  This method returns the number of the first target 
//		which responds TRUE to isDevice, or -1 if none responds TRUE. 
//		Remember that isDevice is over-ridden by sub-class implementations.
//		This means that for instances of the SCSI super class findDevice:
//		will return the number of the next valid SCSI target of any type; 
// 		for instances of a specific sub-class findDevice: will return the
//	 	number of the next device which that sub-class type recognizes.
//
//	SEE ALSO
//		findDevice, isDevice
//
//	----------------------------------------------------------------------

- (int) findDevice: (int) trg
	{
	
	int result = -1, oldTarg = target, oldLun = lun;
	BOOL wasOpen = isOpen; 
	
	if (wasOpen || ![self openTarget: 0 lun: 0])
		{
		
		for(result = trg; result < 8; result++)
			{
			if( ![self setTarget: result lun: 0] && [self isDevice] )
				{
				break;			
				}
			}
		
		if (result == 8)
			{
			result = -1;
			sprintf(errorString, "No target device found.");
			}

		if (oldTarg != -1)
			{			
			[self setTarget: oldTarg lun: oldLun];			
			}
			
		if (!wasOpen && [self close])
			{
			result = -1;
			}	

		}
				
	return(result);

	}


//	----------------------------------------------------------------------
//
//	NAME
//		findDevice
//
//	RETURNS
//		(int)			Target # of first SCSI device that the sub-class
//						can handle.
//
//	EXPLANATION
//		This returns the target number of the first SCSI device that the
//		subclass can handle.  Implemented as a wrap for the method
//		[self findDevice: 0]
//
//	SEE ALSO
//		findDevice, isDevice
//	
//	----------------------------------------------------------------------

- (int) findDevice
	{
	return [self findDevice: 0];
	}


//	----------------------------------------------------------------------
//
//	NAME
//		isDevice
//
//	RETURNS
//		(BOOL)			YES if sub-class can handle device, NO otherwise. 
//
//	EXPLANATION
//		It is the sub-classes responsibility to override this method. 
//		The method should return YES if the subclass of SCSI can handle
//		the device at the current target, NO otherwise.  The SCSI super-
//		class returns YES as long as there is a device attached, no
//		matter what type it is. 
//
//	SEE ALSO
//		findDevice, findDevice:
//	
//	----------------------------------------------------------------------
	
- (BOOL) isDevice
	{
	
	struct inquiry2_reply ibuffer;
	
	if (![self inquiry: &ibuffer])
		{
		return YES;
		}
	
	return NO;
	
	}



//	**********************************************************************
//
//	Instance methods for implementing commonly used SCSI commands.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		inquiry:
//
//	PARAMETERS
//		(struct inquiry2_reply *) ibuffer 	Structure used to return 
//											inquiry data.
//
//	RETURNS
//		(int) 								0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªInquiryº (command 
//		group 0, code 0x12). It returns the inquiry data in 
//		the inquiry2_reply structure defined in "scsi2reg.h"
//
//	----------------------------------------------------------------------
	
- (int) inquiry: (struct inquiry2_reply *) ibuffer
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;
	
	if (wasOpen || ![self open])
		{

		[SCSI clearCommandBlock: (union cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_INQUIRY;
		cdbp->c6_lun    = lun;
		cdbp->c6_len	= 36;

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *) ibuffer;
		sr.sr_dma_max   = 36;
		sr.sr_ioto		= 10;

		result = [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}
		
		}
	
	return(result);		

	}


//	----------------------------------------------------------------------
//
//	NAME
//		readCapacity 
//
//	PARAMETERS
//		(struct capacity2_reply *) rbuffer 	Structure used to return 
//											capacity data.
//
//	RETURNS
//		(int) 								0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªRead Capacityº 
//		(command group 1, code 0x25) for direct-access devices.  It 
//		returns read capacity data (logical unit capacity and block
//		length) in rbuffer, which is a capacity2_reply structure 
//		defined in "SCSI2reg.h"
//
//	----------------------------------------------------------------------

- (int) readCapacity: (struct capacity2_reply *) rbuffer
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb2_10 *cdbp = (struct cdb2_10 *)&sr.sr_cdb.cdb_c10;
	
	if (wasOpen || ![self open])
		{

		[SCSI clearCommandBlock: (union cdb *) cdbp];
		
		cdbp->c10_opcode = C10OP_READCAPACITY;
		cdbp->c10_lun    = lun;
		
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *) rbuffer;
		sr.sr_dma_max   = 8;
		sr.sr_ioto		= 10;
	
		result = [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}	

		}
		
	return(result);		

	}


//	----------------------------------------------------------------------
//
//	NAME
//		requestSense:
//
//	PARAMETERS
//		(struct esense2_reply *) buffer 	Structure used to return 
//										extended sense data.
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªRequest Senseº 
//		(command group 0, code 0x03).  It returns the sense data in 
//		buffer, which is a esenseReply structure defined in 
//		<nextdev/scsireg.h>.
//
//	----------------------------------------------------------------------

- (int)	requestSense: (struct esense2_reply *) ebuffer
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	if (wasOpen || ![self open])
		{
		
		[SCSI clearCommandBlock: (union cdb *) cdbp];

		cdbp->c6_opcode = C6OP_REQSENSE;
		cdbp->c6_lun    = lun;
		cdbp->c6_len	= sizeof(*ebuffer);

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)ebuffer;
		sr.sr_dma_max   = sizeof(*ebuffer);
		sr.sr_ioto		= 10;
	
		result = [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}	
		
		}
		
	return(result);		

	}


//	----------------------------------------------------------------------
//
//	NAME
//		testUnitReady 
//
//	RETURNS
//		(int) 							0 = unit ready, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªTest Unit Readyº 
//		(command group 0, code 0x00).  Returns 0 if no error (unit ready),
//		non-zero otherwise.
//
//	----------------------------------------------------------------------

- (int)	testUnitReady
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	if (wasOpen || ![self open])
		{
	
		[SCSI clearCommandBlock: (union cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_TESTRDY;
		cdbp->c6_lun    = lun;
		cdbp->c6_len	= 0;
	
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *) 0;
		sr.sr_dma_max   = 0;
		sr.sr_ioto		= 10;
	
		result = [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}	

		}

	return result;		
	
	}


//	----------------------------------------------------------------------
//
//	NAME
//		receiveDiagnosticAlloc: buffer:
//
//	PARAMETERS
//		(int) alloc						Length of diagnostic data.
//		(union esenseReply *) buffer 	Structure used to return 
//										diagnostic data.
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This executes the SCSI-2  ªReceive Diagnosticº command (command 
//		group 0, code 0x1C).  Allocation length of the buffer must be 
//		placed into alloc and a pointer to an allocated buffer space must
//		be passed in buffer.  It is the subclass/developer's 
//		responsibility to parse the diagnostic information. 
//
//	SEE ALSO
//		sendDiagnosticPf: selfTest: deviceOffLine: unitOffLine: 
//						  parameterListLength: parameterBuffer:
//
//	----------------------------------------------------------------------

- (int)	receiveDiagnosticAlloc: (int) alloc buffer: (char *) buffer
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	if (wasOpen || ![self open])
		{
	
		[SCSI clearCommandBlock: (union cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_RECEIVEDIAG;
		cdbp->c6_lun    = lun;
		cdbp->c6_len	= alloc;
	
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *) buffer;
		sr.sr_dma_max   = alloc;
		sr.sr_ioto		= 10;
	
		result =  [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}	

		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		sendDiagnosticPf: selfTest: deviceOffLine: unitOffLine: 
//						  parameterListLength: parameterBuffer:
//
//	PARAMETERS
//		(BOOL) pf				Page format 
//		(BOOL) st				Self test
//		(BOOL) dol				Device off line
//		(BOOL) uol				Unit off line
//		(int) pll				Parameter list length
//		(char *pbuf)			Parameter buffer
//
//	RETURNS
//		(int) 					0 = success, -1 = error
//
//	EXPLANATION
//		This executes the SCSI-2  ªSend Diagnosticº command (command group 0,  
//		code 0x1D).  See SCSI-2 Spec for more complete description of
//		diagnostic parameters.
//
//	SEE ALSO
//	 	receiveDiagnostic: buffer:
//
//	----------------------------------------------------------------------

- (int)	sendDiagnosticPf: (BOOL) pf selfTest: (BOOL) st  deviceOffLine: (BOOL) dol 
unitOffLine: (BOOL) uol	parameterListLength: (int) pll parameterBuffer: (char *) pbuf
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb_send_diagnostic *cdbp = (struct cdb_send_diagnostic *)&sr.sr_cdb.cdb_c6;	

	if (wasOpen || ![self open])
		{
						
		[SCSI clearCommandBlock: (union cdb *) cdbp];
	
		cdbp->sd_opcode 	= C6OP_SENDDIAG;
		cdbp->sd_lun 		= lun;
		cdbp->sd_pf			= pf;
		cdbp->sd_selftest 	= st;
		cdbp->sd_devofl		= dol;
		cdbp->sd_unitofl	= uol;
		
		sr.sr_dma_dir	= SR_DMA_WR;
		sr.sr_addr		= pbuf;	
		sr.sr_dma_max   = pll;
		sr.sr_ioto		= 60*60*60;
		
		result = [self performRequest];

		if (!wasOpen && [self close])
			{
			result = -1;
			}	
	
		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		modeSensePage: pc: dbd: mpbuf:
//
//	PARAMETERS
//		(int) page				page code
//		(int) pc 				page control
//		(BOOL) dbd				mpbuf
//
//	RETURNS
//		(int) 					0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªMode Senseº 
//		(code 0x1A).  The page code must be passed in page, the page 
//		control code in pc, the disabled block descriptor in dbd.   
//		Information is returned in mpbuf.
//
//	----------------------------------------------------------------------

- (int) modeSensePage: (int) page pc: (int) pc dbd: (BOOL) dbd mpbuf: (struct mode_parameters *) mpbuf;
	{	

	struct cdb_mode_sense *cdbp = (struct cdb_mode_sense *)&sr.sr_cdb.cdb_c6;
	int result = -1, bdc, pageDataLength, count, pdOffset = 0;
	char *pageBeginning;
	BOOL wasOpen = isOpen;

	if (wasOpen || ![self open])
		{
			  	
		[SCSI clearCommandBlock: (union cdb *) cdbp];
		
		cdbp->ms_opcode 			= C6OP_MODESENSE;
		cdbp->ms_lun				= lun;
		cdbp->ms_allocationLength	= 200;
		cdbp->ms_dbd				= dbd;
		cdbp->ms_pageCode			= page;
		cdbp->ms_pageControl		= pc;		
	
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)mpbuf;	
		sr.sr_dma_max   = 256;
		sr.sr_ioto		= 10;
	
		result =  [self performRequest];
	
		bdc = mpbuf->mph.dad.blockDescriptorLength/8;
		writeBigEndianLong (bdc, &mpbuf->BE_blockDescriptorCount);
				
		for(count = 0; count < bdc; count++ )
			{
			mpbuf->mpbd[count] = * (struct ModeParameterBlockDescriptor *)((char *)mpbuf + 4 + (8 * count));
			}
			
		pageDataLength = mpbuf->mph.dad.modeDataLength - 3 - mpbuf->mph.dad.blockDescriptorLength;
		pageBeginning = (char *)mpbuf + 4 + mpbuf->mph.dad.blockDescriptorLength;
		
		if(pageDataLength > 2)
			{
		
			for( count = 0; (pdOffset < pageDataLength) && (count < MAXPAGES); count++)
				{
				mpbuf->mpp[count]= * ((union PageFormat *) pageBeginning + pdOffset);			
				if( mpbuf->mpp[count].genericPage.pageHeader.pageLength == 0)
					{
					break;
					}		
				pdOffset += mpbuf->mpp[count].genericPage.pageHeader.pageLength;
				}	
			
			}
		else
			{
			count = 0;
			result = -1;
			}
		
		writeBigEndianLong(count, &mpbuf->BE_pageCount);
	
		if (!wasOpen && [self close])
			{
			result = -1;
			}	
	
		}
		
	return result;

	}


//	**********************************************************************
//
//	Instance methods for low-level SCSI access.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		executeRequest
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if request failed.
//
//	EXPLANATION
//		This calls the generic SCSI driver with the parameters as set 
//		in the structure sp. This is a low level method which should only
//		be called by other objects which wish to make SCSI driver 
//		command requests. Subclasses of SCSI should use the 
//		performSCSIRequest method instead. Error codes, if any, will be 
//		returned in the sp structure in the predetermined places 
//		(see <nextdev/scsireg.h> for the structure of sp).  This method
//		does not open or close the SCSI device for the caller.
//	
//	----------------------------------------------------------------------

- (int) executeRequest: (struct scsi_req *) sp 
	{

	int error;
	sr = *sp;
	error = [self performRequest];
	*sp = sr;
	return error;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		executeBusReset
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if ioctl call failed.
//
//	EXPLANATION
//		This performs a software RESET of the SCSI bus. It can only be
//		performed by someone running as the superuser and should be 
//		used cautiously. See the sg (4) manual page for more information.
//	
//	----------------------------------------------------------------------

- (int) executeBusReset
	{

	if (ioctl(fd,SGIOCRST,NULL) < 0) 
		{
		return errno;
		}
	return 0;

	} 



//	**********************************************************************
//
//	Internal (private!) Instance Methods
//	!!! ONLY SUBCLASSES SHOULD CALL THESE PRIVATE METHODS !!!
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		performRequest
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if ioctl call failed,
//							sr.sr_io_status if request returned an error.
//
//	EXPLANATION
//		This calls the generic SCSI driver with the parameters as set 
//		in instance variable sr. This is a private method which should 
//		only be called by subclasses of SCSI which wish to make SCSI 
//		driver command requests.  This method does NOT open or close
//		the SCSI device for the caller.
//	
//	----------------------------------------------------------------------
	
- (int) performRequest
	{

	char errorstr[512];
		
	if (ioctl(fd, SGIOCREQ, &sr) < 0) 
		{
		sprintf(errorstr,"IOctl failed! errno = %d",errno);
		NXLogError(errorstr);
		return errno;
		}
	
	if(sr.sr_io_status || sr.sr_scsi_status) 
		{
		sprintf(errorstr,"IOReq failed! sr_io_status = %d, sr_scsi_status = %d msg = %s",
				sr.sr_io_status,sr.sr_scsi_status,[self returnError: self]);
		NXLogError(errorstr);
		return sr.sr_io_status;
		}
			
	return 0;

	} 

	
@end
