/*
 *
 *	SupraLister
 *
 *	Copyright 1991 by Supra Corporation
 *
 *	Example program showing how to use SCSI direct.
 *
 *	This program scans the SCSI bus looking for SCSI
 *	devices.  When it finds a device it prints the
 *	information returned by the SCSI Inquiry command.
 *
 *
 *	Version 1.0	July 19, 1991
 *
 *	This example was compiled with SAS C 5.10A
 *		lc -L SupraLister.c
 *
 *	This code is based on the draft SCSI 2 specification
 *	revision 10c.
 *
 *	This source code is released for the private use of
 *	Supra's customers.  It may not be used in any
 *	commercial products.
 *
 *	This code is distributed without any warranties,
 *	Supra Corporatoin is not responsible for any damages 
 *	that may occur due to it's use.
 */

#include <exec/types.h>
#include <exec/io.h>
#include <devices/scsidisk.h>

#include <proto/exec.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

/*
 * Declare prototypes for functions
 */
#ifdef	__STDC__			/* Is this an ANSI C compiler? */

extern short	sd_inquiry(void);
extern void	examine_the_device(int board, int device, int lun);
extern void	main(void);

#endif

/*
 * Defines
 */
#define NUM_BOARDS		4		/* number of boards to check	*/

#define	INQUIRY_SIZE		36		/* length of inquiry data	*/ 

#define	SCSI_INQUIRY		0x12


/*
 * Structure that makes 6 byte SCSI commands easier to setup
 */
struct ScsiCmdBlock {
	UBYTE	scb_command;	/* command opcode			*/
	UBYTE	scb_addrHi;	/* high byte of address			*/
	UBYTE	scb_addrMd;	/* middle byte of address		*/
	UBYTE	scb_addrLo;	/* low byte of address			*/
	UBYTE	scb_argmnt;	/* count or interleave value		*/
	UBYTE	scb_vendor;	/* vendor byte -- seek algorithm	*/
};

/*
 * Local variables
 */

		/* SCSI device driver to use */
char	*scsidevice  = "suprascsi.device";

struct	IOStdReq	scsi_iob;
struct	MsgPort		scsi_mp;

unsigned char		buffer[INQUIRY_SIZE];

		/* setup buffers with the SCSI command needed */
struct	ScsiCmdBlock	inqCMD	 = {SCSI_INQUIRY,0,0,0,INQUIRY_SIZE,0};

		/* structure to access SCSI direct, defined in scsidisk.h */
struct	SCSICmd 	ScsiCmd;

		/*
		 * device type strings
		 *
		 * The order of entries is important since
		 * this is indexed by SCSI 2 device type.
		 */
char	*device_types[] =
{
	"Direct-access device.",
	"Sequential-access device.",
	"Printer device.",
	"Processor device.",
	"Write-once device.",
	"CD-ROM device.",
	"Scanner device.",
	"Optical memory device."
};

/*
 * SCSI Inquiry command
 */
short
sd_inquiry()
{
	ScsiCmd.scsi_Data      = (UWORD *)buffer;
	ScsiCmd.scsi_Length    = INQUIRY_SIZE;
	ScsiCmd.scsi_Command   = (UBYTE *)&inqCMD;
	ScsiCmd.scsi_CmdLength = 6;
	ScsiCmd.scsi_Flags     = SCSIF_READ;

	scsi_iob.io_Command = HD_SCSICMD;
	scsi_iob.io_Data    = (APTR)&ScsiCmd;
	scsi_iob.io_Length  = (ULONG)sizeof(struct SCSICmd);

	DoIO(&scsi_iob);

	return((short)scsi_iob.io_Error);
}

/*
 * Routine to examine the device that may or may not
 * exist at this SCSI id.
 */
void
examine_the_device(board, device, lun)
int	board;
int	device;
int	lun;
{
	short		error;
	char		str[20];
	short 		temp;
	short		ansi;

	/*
	 * Issue an Inquiry command to the device.
	 *
	 * The SCSI standards, SCSI 1 and the SCSI 2 draft, state
	 * that all SCSI devices must support the INQUIRY command
	 * and should respond to it even if error conditions exist
	 * on the device.  The INQUIRY command should also work
	 * immediately after power up or reset, if some data is
	 * stored on the media zeros or ASCII spaces can be
	 * returned in those fields.
	 *
	 * Unfortunately reality is some what different.  Some old
	 * SCSI devices (in particular the Adaptec ACB4000 and
	 * ACB 4070) do not support the INQUIRY command.  Other
	 * devices which do support it may not always respond if
	 * error conditions exist on the device.  Some devices
	 * do not respond for long periods after powerup (some
	 * Seagate drives won't respond for several seconds).
	 * Also some older devices wrap LUNs, so that LUN 0 is
	 * the same as LUN 2 and LUN 1 is the same as LUN 3, etc.
	 * And finally some devices won't respond and may not
	 * even be selectable while handling outside events like
	 * media insertion.
	 *
	 * SASI devices (Shugart Associates Systems Interface)
	 * do not support INQUIRY commands since they predate the
	 * SCSI standards.
	 * 
	 * So issuing an INQUIRY command may or may not tell you that
	 * a device is present.  And if a device is present it may not
	 * tell you what device it is.
	 *
	 */

	error = sd_inquiry();

	if (error)
	{
		switch (error)
		{
		case HFERR_SelTimeout :
			/*
			 * Unable to select a SCSI device, which should
			 * mean that no device is present.
			 */
			return;
	
		case HFERR_BadStatus :
			/*
			 * A device is present but returned an error
			 * status value.  The returned INQUIRY data,
			 * if any, may or may not be valid.
			 *
			 * If any data was returned assume it is valid,
			 * otherwise just exit.  A smarter program would
			 * try and clear the error status from the device
			 * and then resend the INQUIRY command.
			 */
			if (ScsiCmd.scsi_Actual == 0)
			{
				printf("Board %d Device %d LUN %d: INQUIRY failed status error %d\n\n", 
					board, device, lun, ScsiCmd.scsi_Status);
				return;
			}
			break;

		default :
			/*
			 * Some other sort of error occured during the
			 * command.
			 *
			 * Just return.
			 */
			return;
		}
	}

	/*
	 * At this point there is a buffer full of INQUIRY data that is
	 * presumed to be valid.  So print it out.
	 */

		/* decode the Peripheral Qualifier and Peripheral Device Type */
	if (buffer[0] == 0x7f)		/* invalid LUN */
	{
		return;
	}

	printf("Board %d Device %d LUN %d:\n", board, device, lun);

	temp = (buffer[0] >> 5) & 0x07;		/* Qualifer	*/
	switch (temp)
	{
	case 0x00 :			/* valid qualifier	*/
		break;
	case 0x01 :		 	/* LUN valid but no device connected */
		printf("\tLUN is valid but not connected.\n");
		return;
	case 0x02 : 			/* reserved?	*/
		printf("\tPeripheral Qualifer is a reserved value.\n");
		break;
	default :			/* all others are vendor specific */
		printf("\tPeripheral Qualifer is vendor specific.\n");
		break;
	}

	temp = buffer[0] & 0x1f;		/* Device Type	*/
	if (temp <= 0x09)
	{
		printf("\t%s\n", device_types[temp]);
	}
	else if (temp == 0x1f)
	{
		printf("\tUnknown or no device type.\n");
	}
	else
	{
		printf("\tDevice type not decoded by this program.\n");
	}

	/*
	 * Don't print all the other information unless on LUN 0
	 */
	if (lun)
	{
		return;
	}

	/*
	 * Removable media?
	 */
	if (buffer[1] & 0x80)
	{
		printf("\tMedia is removable.\n");
	}

	/*
	 * Print the vendor and product identifications
	 */
	strncpy(str, &buffer[8], 8);		/* vendor identification */
	str[8] = '\0';
	printf("\tVendor:  %8s\n", str);

	strncpy(str, &buffer[16], 16);		/* product identification */
	str[16] = '\0';
	printf("\tProduct: %16s", str);

	strncpy(str, &buffer[32], 4);
	str[4] = '\0';
	printf("\tRevision: %4s\n", str);

	/*
	 * What SCSI standards does the device claim to follow?
	 */
	ansi = buffer[2] & 0x07;
	printf("\tANSI version %d.\n", ansi);

	temp = (buffer[2] >> 3) & 0x07;
	printf("\tECMA version %d.\n", temp);

	temp = (buffer[2] >> 6) & 0x07;
	printf("\tISO  version %d.\n", temp);

	/*
	 * Print the various options that may be supported
	 */

	if (ansi > 1)				/* this field is vendor
						 * unique under SCSI 1
						 */
	{
		if (buffer[7] & 0x80)
		{
			printf("\tRelative addressing is supported.\n");
		}
	
		if (buffer[7] & 0x40)
		{
			printf("\t32 bit wide data transfers are supported.\n");
		}
	
		if (buffer[7] & 0x20)
		{
			printf("\t16 bit wide data transfers are supported.\n");
		}
	
		if (buffer[7] & 0x10)
		{
			printf("\tSynchronous data transfers are supported.\n");
		}
	
		if (buffer[7] & 0x08)
		{
			printf("\tLinked commands are supported.\n");
		}
	
		/* bit 2 is reserved */
	
		if (buffer[7] & 0x02)
		{
			printf("\tCommand queuing is supported.\n");
		}

		if (buffer[7] & 0x01)
		{
			printf("\tDevice uses soft resets.\n");
		}
		else
		{
			printf("\tDevice uses hard resets.\n");
		}
	
	}

	putchar('\n');
}

/*
 * Main code
 */
void
main()
{
	int	board, device, lun;
	long	unitnum;

	printf("\nSupraLister version 1.0\n");
	printf("Copyright 1991 by Supra Corporation\n");
	printf("By Daniel L. Moore\n\n");

	/* setup for the i/o */
	scsi_mp.mp_SigTask = FindTask(0L);
	scsi_mp.mp_Node.ln_Type = NT_MSGPORT;
	scsi_mp.mp_Node.ln_Name = NULL;
	scsi_mp.mp_Node.ln_Pri = 0;
	NewList(&scsi_mp.mp_MsgList);
	scsi_iob.io_Message.mn_ReplyPort = &scsi_mp;

	/*
	 * For the sample program only look at LUN 0
	 */
	lun = 0;

	/*
	 * Check all the boards and all the devices on those boards
	 */
	for (board = 0; board < NUM_BOARDS; board++)
	{
		/*
		 * On Supra SCSI controllers the initator ID is 7.
		 * So the legal SCSI device numbers are 0 to 6.
		 */
		for (device = 0; device < 7; device++)
		{
			unitnum = (long)(board*100) + (lun*10) + device;
			if(OpenDevice(scsidevice,unitnum,&scsi_iob,0) != 0)
			{
				continue;	/* just try the next one */
			}

			examine_the_device(board, device, lun);

			CloseDevice(&scsi_iob);
		}
	}
}


