/*
 * This file has been modified for the cdrkit suite.
 *
 * The behaviour and appearence of the program code below can differ to a major
 * extent from the version distributed by the original author(s).
 *
 * For details, see Changelog file distributed with the cdrkit package. If you
 * received this file from another source then ask the distributing person for
 * a log of modifications.
 *
 */

/* @(#)scsi-linux-sg.c	1.86 05/11/22 Copyright 1997 J. Schilling */
/*
 *	Interface for Linux generic SCSI implementation (sg).
 *
 *	This is the interface for the broken Linux SCSI generic driver.
 *	This is a hack, that tries to emulate the functionality
 *	of the usal driver.
 *
 *	Design flaws of the sg driver:
 *	-	cannot see if SCSI command could not be send
 *	-	cannot get SCSI status byte
 *	-	cannot get real dma count of tranfer
 *	-	cannot get number of bytes valid in auto sense data
 *	-	to few data in auto sense (CCS/SCSI-2/SCSI-3 needs >= 18)
 *
 *	This code contains support for the sg driver version 2 by
 *		H. Eißfeld & J. Schilling
 *	Although this enhanced version has been announced to Linus and Alan,
 *	there was no reaction at all.
 *
 *	About half a year later there occured a version in the official
 *	Linux that was also called version 2. The interface of this version
 *	looks like a playground - the enhancements from this version are
 *	more or less useless for a portable real-world program.
 *
 *	With Linux 2.4 the official version of the sg driver is called 3.x
 *	and seems to be usable again. The main problem now is the curious
 *	interface that is provided to raise the DMA limit from 32 kB to a
 *	more reasonable value. To do this in a reliable way, a lot of actions
 *	are required.
 *
 *	Warning: you may change this source, but if you do that
 *	you need to change the _usal_version and _usal_auth* string below.
 *	You may not return "schily" for an SCG_AUTHOR request anymore.
 *	Choose your name instead of "schily" and make clear that the version
 *	string is related to a modified source.
 *
 *	Copyright (c) 1997 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <linux/version.h>
#include <sys/types.h>
#include <dirent.h>

#ifndef LINUX_VERSION_CODE	/* Very old kernel? */
#	define LINUX_VERSION_CODE 0
#endif

#if LINUX_VERSION_CODE >= 0x01031a /* <linux/scsi.h> introduced in 1.3.26 */
#if LINUX_VERSION_CODE >= 0x020000 /* <scsi/scsi.h> introduced somewhere. */
/* Need to fine tune the ifdef so we get the transition point right. */
#include <scsi/scsi.h>
#else
#include <linux/scsi.h>
#endif
#else				/* LINUX_VERSION_CODE == 0 Very old kernel? */
#define	__KERNEL__		/* Some Linux Include files are inconsistent */
#include <linux/fs.h>		/* From ancient versions, really needed? */
#undef __KERNEL__
#include "block/blk.h"		/* From ancient versions, really needed? */
#include "scsi/scsi.h"
#endif

#if	defined(HAVE_BROKEN_SCSI_SG_H) || \
	defined(HAVE_BROKEN_SRC_SCSI_SG_H)
/*
 * Be very careful in case that the Linux Kernel maintainers
 * unexpectedly fix the bugs in the Linux Lernel include files.
 * Only introduce the attempt for a workaround in case the include
 * files are broken anyway.
 */
#define	__user
#endif
#include "scsi/sg.h"
#if	defined(HAVE_BROKEN_SCSI_SG_H) || \
	defined(HAVE_BROKEN_SRC_SCSI_SG_H)
#undef	__user
#endif

#undef sense			/* conflict in struct cdrom_generic_command */
#include <linux/cdrom.h>

#if	defined(CDROM_PACKET_SIZE) && defined(CDROM_SEND_PACKET)
#define	USE_OLD_ATAPI
#endif

/*
 *	Warning: you may change this source, but if you do that
 *	you need to change the _usal_version and _usal_auth* string below.
 *	You may not return "schily" for an SCG_AUTHOR request anymore.
 *	Choose your name instead of "schily" and make clear that the version
 *	string is related to a modified source.
 */
static	char	_usal_trans_version[] = "scsi-linux-sg.c-1.86";	/* The version for this transport*/

#ifndef	SCSI_IOCTL_GET_BUS_NUMBER
#define	SCSI_IOCTL_GET_BUS_NUMBER 0x5386
#endif

/*
 * XXX There must be a better way than duplicating things from system include
 * XXX files. This is stolen from /usr/src/linux/drivers/scsi/scsi.h
 */
#ifndef	DID_OK
#define	DID_OK		0x00 /* NO error				*/
#define	DID_NO_CONNECT	0x01 /* Couldn't connect before timeout period	*/
#define	DID_BUS_BUSY	0x02 /* BUS stayed busy through time out period	*/
#define	DID_TIME_OUT	0x03 /* TIMED OUT for other reason		*/
#define	DID_BAD_TARGET	0x04 /* BAD target.				*/
#define	DID_ABORT	0x05 /* Told to abort for some other reason	*/
#define	DID_PARITY	0x06 /* Parity error				*/
#define	DID_ERROR	0x07 /* Internal error				*/
#define	DID_RESET	0x08 /* Reset by somebody.			*/
#define	DID_BAD_INTR	0x09 /* Got an interrupt we weren't expecting.	*/
#endif

/*
 *  These indicate the error that occurred, and what is available.
 */
#ifndef	DRIVER_BUSY
#define	DRIVER_BUSY	0x01
#define	DRIVER_SOFT	0x02
#define	DRIVER_MEDIA	0x03
#define	DRIVER_ERROR	0x04

#define	DRIVER_INVALID	0x05
#define	DRIVER_TIMEOUT	0x06
#define	DRIVER_HARD	0x07
#define	DRIVER_SENSE	0x08
#endif

/*
 * XXX Should add extra space in buscookies and usalfiles for a "PP bus"
 * XXX and for two or more "ATAPI busses".
 */
#define	MAX_SCG		256	/* Max # of SCSI controllers */
#define	MAX_TGT		16
#define	MAX_LUN		8

#ifdef	USE_OLD_ATAPI
/*
 * # of virtual buses (schilly_host number)
 */
#define	MAX_SCHILLY_HOSTS	MAX_SCG
typedef struct {
	Uchar   typ:4;
	Uchar   bus:4;
	Uchar   host:8;
} ata_buscookies;
#endif

struct usal_local {
	int	usalfile;		/* Used for SG_GET_BUFSIZE ioctl()*/
	short	usalfiles[MAX_SCG][MAX_TGT][MAX_LUN];
	short	buscookies[MAX_SCG];
	int	pgbus;
	int	pack_id;		/* Should be a random number	*/
	int	drvers;
	short	isold;
	short	flags;
	long	xbufsize;
	char	*xbuf;
	char	*SCSIbuf;
#ifdef	USE_OLD_ATAPI
	ata_buscookies	bc[MAX_SCHILLY_HOSTS];
#endif
};
#define	usallocal(p)	((struct usal_local *)((p)->local))

/*
 * Flag definitions
 */
#define	LF_ATA		0x01		/* Using /dev/hd* ATA interface	*/

#ifdef	SG_BIG_BUFF
#define	MAX_DMA_LINUX	SG_BIG_BUFF	/* Defined in include/scsi/sg.h	*/
#else
#define	MAX_DMA_LINUX	(4*1024)	/* Old Linux versions		*/
#endif

#ifndef	SG_MAX_SENSE
#	define	SG_MAX_SENSE	16	/* Too small for CCS / SCSI-2	*/
#endif					/* But cannot be changed	*/

#if	!defined(__i386) && !defined(i386) && !defined(mc68000)
#define	MISALIGN
#endif
/*#define	MISALIGN*/
/*#undef	SG_GET_BUFSIZE*/

#if	defined(USE_PG) && !defined(USE_PG_ONLY)
#include "scsi-linux-pg.c"
#endif
#ifdef	USE_OLD_ATAPI
#include "scsi-linux-ata.c"
#endif


#ifdef	MISALIGN
static	int	sg_getint(int *ip);
#endif
static	int	usalo_send(SCSI *usalp);
#ifdef	SG_IO
static	int	sg_rwsend(SCSI *usalp);
#endif
static	void	sg_clearnblock(int f);
static	BOOL	sg_setup(SCSI *usalp, int f, int busno, int tgt, int tlun, 
								int ataidx);
static	void	sg_initdev(SCSI *usalp, int f);
static	int	sg_mapbus(SCSI *usalp, int busno, int ino);
static	BOOL	sg_mapdev(SCSI *usalp, int f, int *busp, int *tgtp, int *lunp,
								 int *chanp, int *inop, int ataidx);
#if defined(SG_SET_RESERVED_SIZE) && defined(SG_GET_RESERVED_SIZE)
static	long	sg_raisedma(SCSI *usalp, long newmax);
#endif
static	void	sg_settimeout(int f, int timeout);

int    sg_open_excl(char *device, int mode);

static BOOL get_max_secs(char *dirpath, int *outval);

int
sg_open_excl(char *device, int mode)

{
       int f;
       int i;
       f = open(device, mode|O_EXCL);
       /* try to reopen locked/busy devices up to five times */
       for (i = 0; (i < 5) && (f == -1 && errno == EBUSY); i++) {
	       fprintf(stderr, "Error trying to open %s exclusively (%s)... %s\n",
               device, strerror(errno), 
               (i<4)?"retrying in 1 second.":"giving up.");
	       usleep(1000000 + 100000.0 * rand()/(RAND_MAX+1.0));
	       f = open(device, mode|O_EXCL);
       }
       return f;
}


/*
 * Return version information for the low level SCSI transport code.
 * This has been introduced to make it easier to trace down problems
 * in applications.
 */
static char *
usalo_version(SCSI *usalp, int what)
{
	if (usalp != (SCSI *)0) {
#ifdef	USE_PG
		/*
		 * If we only have a Parallel port or only opened a handle
		 * for PP, just return PP version.
		 */
		if (usallocal(usalp)->pgbus == 0 ||
		    (usal_scsibus(usalp) >= 0 &&
		    usal_scsibus(usalp) == usallocal(usalp)->pgbus))
			return (pg_version(usalp, what));
#endif
		switch (what) {

		case SCG_VERSION:
			return (_usal_trans_version);
		/*
		 * If you changed this source, you are not allowed to
		 * return "schily" for the SCG_AUTHOR request.
		 */
		case SCG_AUTHOR:
			return (_usal_auth_cdrkit);
		case SCG_SCCS_ID:
			return (__sccsid);
		case SCG_KVERSION:
			{
				static	char kv[16];
				int	n;

				if (usallocal(usalp)->drvers >= 0) {
					n = usallocal(usalp)->drvers;
					snprintf(kv, sizeof (kv),
					"%d.%d.%d",
					n/10000, (n%10000)/100, n%100);

					return (kv);
				}
			}
		}
	}
	return ((char *)0);
}

static int
usalo_help(SCSI *usalp, FILE *f)
{
	__usal_help(f, "sg", "Generic transport independent SCSI",
		"", "bus,target,lun", "1,2,0", TRUE, FALSE);
#ifdef	USE_PG
	pg_help(usalp, f);
#endif
#ifdef	USE_OLD_ATAPI
	usalo_ahelp(usalp, f);
#endif
	__usal_help(f, "ATA", "ATA Packet specific SCSI transport using sg interface",
		"ATA:", "bus,target,lun", "1,2,0", TRUE, FALSE);
	return (0);
}

static int
usalo_open(SCSI *usalp, char *device)
{
		int	busno	= usal_scsibus(usalp);
		int	tgt	= usal_target(usalp);
		int	tlun	= usal_lun(usalp);
	register int	f;
	register int	i;
	register int	b;
	register int	t;
	register int	l;
	register int	nopen = 0;
	char		devname[64];
		BOOL	use_ata = FALSE;


	if (busno >= MAX_SCG || tgt >= MAX_TGT || tlun >= MAX_LUN) {
		errno = EINVAL;
		if (usalp->errstr)
			snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
				"Illegal value for busno, target or lun '%d,%d,%d'",
				busno, tgt, tlun);
		return (-1);
	}
	if (device != NULL && *device != '\0') {
#ifdef	USE_OLD_ATAPI
		if (strncmp(device, "ATAPI", 5) == 0) {
			usalp->ops = &ata_ops;
			return (SCGO_OPEN(usalp, device));
		}
#endif
		if (strcmp(device, "ATA") == 0) {
			use_ata = TRUE;
			device = NULL;
		}
	}

	if (usalp->local == NULL) {
		usalp->local = malloc(sizeof (struct usal_local));
		if (usalp->local == NULL)
			return (0);

		usallocal(usalp)->usalfile = -1;
		usallocal(usalp)->pgbus = -2;
		usallocal(usalp)->SCSIbuf = (char *)-1;
		usallocal(usalp)->pack_id = 5;
		usallocal(usalp)->drvers = -1;
		usallocal(usalp)->isold = -1;
		usallocal(usalp)->flags = 0;
		if (use_ata)
			usallocal(usalp)->flags |= LF_ATA;
		usallocal(usalp)->xbufsize = 0L;
		usallocal(usalp)->xbuf = NULL;

		for (b = 0; b < MAX_SCG; b++) {
			usallocal(usalp)->buscookies[b] = (short)-1;
			for (t = 0; t < MAX_TGT; t++) {
				for (l = 0; l < MAX_LUN; l++)
					usallocal(usalp)->usalfiles[b][t][l] = (short)-1;
			}
		}
	}

	if (use_ata)
		goto scanopen;

	if ((device != NULL && *device != '\0') || (busno == -2 && tgt == -2))
		goto openbydev;

scanopen:
	if (use_ata) {
		for (i=2*busno+tgt >= 0 ? 2*busno+tgt:0; i <= 25; i++) {
			snprintf(devname, sizeof (devname), "/dev/hd%c", i+'a');
			/* O_NONBLOCK is dangerous */
			f = sg_open_excl(devname, O_RDWR | O_NONBLOCK);
			if (f < 0) {
				/*
				 * Set up error string but let us clear it later
				 * if at least one open succeeded.
				 */
				if (usalp->errstr)
					snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
							"Cannot open '%s'", devname);
				if (errno != ENOENT && errno != ENXIO && errno != ENODEV) {
					if (usalp->errstr)
						snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
								"Cannot open '%s'", devname);
					continue;
				}
			} else {
				int	iparm;

				if (ioctl(f, SG_GET_TIMEOUT, &iparm) < 0) {
					if (usalp->errstr)
						snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
								"SCSI unsupported with '%s'", devname);
					close(f);
					continue;
				}
				sg_clearnblock(f);	/* Be very proper about this */
				if (sg_setup(usalp, f, busno, tgt, tlun, i))
					return (++nopen);
				if (busno < 0 && tgt < 0 && tlun < 0)
					nopen++;
			}
		}

		if(nopen==0)
			return(0);
	}
	if (nopen > 0 && usalp->errstr)
		usalp->errstr[0] = '\0';

	if (nopen == 0) {
		for (i = 0; i < 32; i++) {
			snprintf(devname, sizeof (devname), "/dev/sg%d", i);
			/* O_NONBLOCK is dangerous */
			f = sg_open_excl(devname, O_RDWR | O_NONBLOCK);
			if (f < 0) {
				/*
				 * Set up error string but let us clear it later
				 * if at least one open succeeded.
				 */
				if (usalp->errstr)
					snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
							"Cannot open '%s'", devname);
				if(errno == EACCES || errno==EPERM)
					continue;
				if (errno != ENOENT && errno != ENXIO && errno != ENODEV) {
					if (usalp->errstr)
						snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
								"Cannot open '%s'", devname);
					return (0);
				}
			} else {
				sg_clearnblock(f);	/* Be very proper about this */
				if (sg_setup(usalp, f, busno, tgt, tlun, -1))
					return (++nopen);
				if (busno < 0 && tgt < 0 && tlun < 0)
					nopen++;
			}
		}
	}
	if (nopen > 0 && usalp->errstr)
		usalp->errstr[0] = '\0';

	if (nopen == 0) {
		for (i = 0; i <= 25; i++) {
			snprintf(devname, sizeof (devname), "/dev/sg%c", i+'a');
			/* O_NONBLOCK is dangerous */
			f = sg_open_excl(devname, O_RDWR | O_NONBLOCK);
			if (f < 0) {
				/*
				 * Set up error string but let us clear it later
				 * if at least one open succeeded.
				 */
				if (usalp->errstr)
					snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
							"Cannot open '%s'", devname);
				if(errno == EACCES || errno==EPERM)
					continue;
				if (errno != ENOENT && errno != ENXIO && errno != ENODEV) {
					if (usalp->errstr)
						snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
								"Cannot open '%s'", devname);
					return (0);
				}
			} else {
				sg_clearnblock(f);	/* Be very proper about this */
				if (sg_setup(usalp, f, busno, tgt, tlun, -1))
					return (++nopen);
				if (busno < 0 && tgt < 0 && tlun < 0)
					nopen++;
			}
		}
	}
	if (nopen > 0 && usalp->errstr)
		usalp->errstr[0] = '\0';

openbydev:

	if (device != NULL && *device != '\0') {
		b = -1;
		if (strlen(device) == 8 && strncmp(device, "/dev/hd", 7) == 0) {
			b = device[7] - 'a';
			if (b < 0 || b > 25)
				b = -1;
		}
    /* O_NONBLOCK is dangerous */
		f = sg_open_excl(device, O_RDWR | O_NONBLOCK);
/*		if (f < 0 && errno == ENOENT)*/
/*			goto openpg;*/

		if (f < 0) {
			/*
			 * The pg driver has the same rules to decide whether
			 * to use openbydev. If we cannot open the device, it
			 * makes no sense to try the /dev/pg* driver.
			 */
			if (usalp->errstr)
				snprintf(usalp->errstr, SCSI_ERRSTR_SIZE,
					"Cannot open '%s'",
					device);
			return (0);
		}

		sg_clearnblock(f);		/* Be very proper about this */
		if (!sg_mapdev(usalp, f, &busno, &tgt, &tlun, 0, 0, b)) {
			close(f);
			/*
			 * If sg_mapdev() failes, this may be /dev/pg* device.
			 */
			goto openpg;
		}

#ifdef	OOO
		if (usal_scsibus(usalp) < 0)
			usal_scsibus(usalp) = busno;
		if (usal_target(usalp) < 0)
			usal_target(usalp) = tgt;
		if (usal_lun(usalp) < 0)
			usal_lun(usalp) = tlun;
#endif

		usal_settarget(usalp, busno, tgt, tlun);
		if (sg_setup(usalp, f, busno, tgt, tlun, b))
			return (++nopen);
	}
openpg:
#ifdef	USE_PG
	nopen += pg_open(usalp, device);
#endif
	if (usalp->debug > 0) for (b = 0; b < MAX_SCG; b++) {
		fprintf((FILE *)usalp->errfile,
			"Bus: %d cookie: %X\n",
			b, usallocal(usalp)->buscookies[b]);
		for (t = 0; t < MAX_TGT; t++) {
			for (l = 0; l < MAX_LUN; l++) {
				if (usallocal(usalp)->usalfiles[b][t][l] != (short)-1) {
					fprintf((FILE *)usalp->errfile,
						"file (%d,%d,%d): %d\n",
						b, t, l, usallocal(usalp)->usalfiles[b][t][l]);
				}
			}
		}
	}
	return (nopen);
}

static int
usalo_close(SCSI *usalp)
{
	register int	f;
	register int	b;
	register int	t;
	register int	l;

	if (usalp->local == NULL)
		return (-1);

	for (b = 0; b < MAX_SCG; b++) {
		if (b == usallocal(usalp)->pgbus)
			continue;
		usallocal(usalp)->buscookies[b] = (short)-1;
		for (t = 0; t < MAX_TGT; t++) {
			for (l = 0; l < MAX_LUN; l++) {
				f = usallocal(usalp)->usalfiles[b][t][l];
				if (f >= 0)
					close(f);
				usallocal(usalp)->usalfiles[b][t][l] = (short)-1;
			}
		}
	}
	if (usallocal(usalp)->xbuf != NULL) {
		free(usallocal(usalp)->xbuf);
		usallocal(usalp)->xbufsize = 0L;
		usallocal(usalp)->xbuf = NULL;
	}
#ifdef	USE_PG
	pg_close(usalp);
#endif
	return (0);
}

/*
 * The Linux kernel becomes more and more unmaintainable.
 * Every year, a new incompatible SCSI transport interface is added.
 * Each of them has it's own contradictory constraints.
 * While you cannot have O_NONBLOCK set during operation, at least one
 * of the drivers requires O_NONBLOCK to be set during open().
 * This is used to clear O_NONBLOCK immediately after open() succeeded.
 */
static void
sg_clearnblock(int f)
{
	int	n;

	n = fcntl(f, F_GETFL);
	n &= ~O_NONBLOCK;
	fcntl(f, F_SETFL, n);
}

static BOOL
sg_setup(SCSI *usalp, int f, int busno, int tgt, int tlun, int ataidx)
{
	int	n;
	int	Chan;
	int	Ino;
	int	Bus;
	int	Target;
	int	Lun;
	BOOL	onetarget = FALSE;

#ifdef	SG_GET_VERSION_NUM
	if (usallocal(usalp)->drvers < 0) {
		usallocal(usalp)->drvers = 0;
		if (ioctl(f, SG_GET_VERSION_NUM, &n) >= 0) {
			usallocal(usalp)->drvers = n;
			if (usalp->overbose) {
				fprintf((FILE *)usalp->errfile,
					"Linux sg driver version: %d.%d.%d\n",
					n/10000, (n%10000)/100, n%100);
			}
		}
	}
#endif
	if (usal_scsibus(usalp) >= 0 && usal_target(usalp) >= 0 && usal_lun(usalp) >= 0)
		onetarget = TRUE;

	sg_mapdev(usalp, f, &Bus, &Target, &Lun, &Chan, &Ino, ataidx);

	/*
	 * For old kernels try to make the best guess.
	 */
	Ino |= Chan << 8;
	n = sg_mapbus(usalp, Bus, Ino);
	if (Bus == -1) {
		Bus = n;
		if (usalp->debug > 0) {
			fprintf((FILE *)usalp->errfile,
				"SCSI Bus: %d (mapped from %d)\n", Bus, Ino);
		}
	}

	if (Bus < 0 || Bus >= MAX_SCG || Target < 0 || Target >= MAX_TGT ||
						Lun < 0 || Lun >= MAX_LUN) {
		return (FALSE);
	}

	if (usallocal(usalp)->usalfiles[Bus][Target][Lun] == (short)-1)
		usallocal(usalp)->usalfiles[Bus][Target][Lun] = (short)f;

	if (onetarget) {
		if (Bus == busno && Target == tgt && Lun == tlun) {
			sg_initdev(usalp, f);
			usallocal(usalp)->usalfile = f;	/* remember file for ioctl's */
			return (TRUE);
		} else {
			usallocal(usalp)->usalfiles[Bus][Target][Lun] = (short)-1;
			close(f);
		}
	} else {
		/*
		 * SCSI bus scanning may cause other generic SCSI activities to
		 * fail because we set the default timeout and clear command
		 * queues (in case of the old sg driver interface).
		 */
		sg_initdev(usalp, f);
		if (usallocal(usalp)->usalfile < 0)
			usallocal(usalp)->usalfile = f;	/* remember file for ioctl's */
	}
	return (FALSE);
}

static void
sg_initdev(SCSI *usalp, int f)
{
	struct sg_rep {
		struct sg_header	hd;
		unsigned char		rbuf[100];
	} sg_rep;
	int	n;
	int	i;
	struct stat sb;

	sg_settimeout(f, usalp->deftimeout);

	/*
	 * If it's a block device, don't read.... pre Linux-2.4 /dev/sg*
	 * definitely is a character device and we only need to clear the
	 * queue for old /dev/sg* versions. If somebody ever implements
	 * raw disk access for Linux, this test may fail.
	 */
	if (fstat(f, &sb) >= 0 && S_ISBLK(sb.st_mode))
		return;

	/* Eat any unwanted garbage from prior use of this device */

	n = fcntl(f, F_GETFL);	/* Be very proper about this */
	fcntl(f, F_SETFL, n|O_NONBLOCK);

	fillbytes((caddr_t)&sg_rep, sizeof (struct sg_header), '\0');
	sg_rep.hd.reply_len = sizeof (struct sg_header);

	/*
	 * This is really ugly.
	 * We come here if 'f' is related to a raw device. If Linux
	 * will ever have raw devices for /dev/hd* we may get problems.
	 * As long as there is no clean way to find out whether the
	 * filedescriptor 'f' is related to an old /dev/sg* or to
	 * /dev/hd*, we must assume that we found an old /dev/sg* and
	 * clean it up. Unfortunately, reading from /dev/hd* will
	 * Access the medium.
	 */
	for (i = 0; i < 1000; i++) {	/* Read at least 32k from /dev/sg* */
		int	ret;

		ret = read(f, &sg_rep, sizeof (sg_rep));
		if (ret > 0)
			continue;
		if (ret == 0 || errno == EAGAIN || errno == EIO)
			break;
		if (ret < 0 && i > 10)	/* Stop on repeated unknown error */
			break;
	}
	fcntl(f, F_SETFL, n);
}

static int
sg_mapbus(SCSI *usalp, int busno, int ino)
{
	register int	i;

	if (busno >= 0 && busno < MAX_SCG) {
		/*
		 * SCSI_IOCTL_GET_BUS_NUMBER worked.
		 * Now we have the problem that Linux does not properly number
		 * SCSI busses. The Bus number that Linux creates really is
		 * the controller (card) number. I case of multi SCSI bus
		 * cards we are lost.
		 */
		if (usallocal(usalp)->buscookies[busno] == (short)-1) {
			usallocal(usalp)->buscookies[busno] = ino;
			return (busno);
		}
		if (usallocal(usalp)->buscookies[busno] != (short)ino)
			errmsgno(EX_BAD, "Warning Linux Bus mapping botch.\n");
		return (busno);

	} else for (i = 0; i < MAX_SCG; i++) {
		if (usallocal(usalp)->buscookies[i] == (short)-1) {
			usallocal(usalp)->buscookies[i] = ino;
			return (i);
		}

		if (usallocal(usalp)->buscookies[i] == ino)
			return (i);
	}
	return (0);
}

static BOOL
sg_mapdev(SCSI *usalp, int f, int *busp, int *tgtp, int *lunp, int *chanp, 
			 int *inop, int ataidx)
{
	struct	sg_id {
		long	l1; /* target | lun << 8 | channel << 16 | low_ino << 24 */
		long	l2; /* Unique id */
	} sg_id;
	int	Chan;
	int	Ino;
	int	Bus;
	int	Target;
	int	Lun;

	if (ataidx >= 0) {
		/*
		 * The badly designed /dev/hd* interface maps everything
		 * to 0,0,0 so we need to do the mapping ourselves.
		 */
		*busp = ataidx / 2;
		*tgtp = ataidx % 2;
		*lunp = 0;
		if (chanp)
			*chanp = 0;
		if (inop)
			*inop = 0;
		return (TRUE);
	}
	if (ioctl(f, SCSI_IOCTL_GET_IDLUN, &sg_id))
		return (FALSE);
	if (usalp->debug > 0) {
		fprintf((FILE *)usalp->errfile,
			"l1: 0x%lX l2: 0x%lX\n", sg_id.l1, sg_id.l2);
	}
	if (ioctl(f, SCSI_IOCTL_GET_BUS_NUMBER, &Bus) < 0) {
		Bus = -1;
	}

	Target	= sg_id.l1 & 0xFF;
	Lun	= (sg_id.l1 >> 8) & 0xFF;
	Chan	= (sg_id.l1 >> 16) & 0xFF;
	Ino	= (sg_id.l1 >> 24) & 0xFF;
	if (usalp->debug > 0) {
		fprintf((FILE *)usalp->errfile,
			"Bus: %d Target: %d Lun: %d Chan: %d Ino: %d\n",
			Bus, Target, Lun, Chan, Ino);
	}
	*busp = Bus;
	*tgtp = Target;
	*lunp = Lun;
	if (chanp)
		*chanp = Chan;
	if (inop)
		*inop = Ino;
	return (TRUE);
}

#if defined(SG_SET_RESERVED_SIZE) && defined(SG_GET_RESERVED_SIZE)
/*
 * The way Linux does DMA resouce management is a bit curious.
 * It totally deviates from all other OS and forces long ugly code.
 * If we are opening all drivers for a SCSI bus scan operation, we need
 * to set the limit for all open devices.
 * This may use up all kernel memory ... so do the job carefully.
 *
 * A big problem is that SG_SET_RESERVED_SIZE does not return any hint
 * on whether the request did fail. The only way to find if it worked
 * is to use SG_GET_RESERVED_SIZE to read back the current values.
 */
static long
sg_raisedma(SCSI *usalp, long newmax)
{
	register int	b;
	register int	t;
	register int	l;
	register int	f;
		int	val;
		int	old;

	/*
	 * First try to raise the DMA limit to a moderate value that
	 * most likely does not use up all kernel memory.
	 */
	val = 126*1024;

	if (val > MAX_DMA_LINUX) {
		for (b = 0; b < MAX_SCG; b++) {
			for (t = 0; t < MAX_TGT; t++) {
				for (l = 0; l < MAX_LUN; l++) {
					if ((f = SCGO_FILENO(usalp, b, t, l)) < 0)
						continue;
					old = 0;
					if (ioctl(f, SG_GET_RESERVED_SIZE, &old) < 0)
						continue;
					if (val > old)
						ioctl(f, SG_SET_RESERVED_SIZE, &val);
				}
			}
		}
	}

	/*
	 * Now to raise the DMA limit to what we really need.
	 */
	if (newmax > val) {
		val = newmax;
		for (b = 0; b < MAX_SCG; b++) {
			for (t = 0; t < MAX_TGT; t++) {
				for (l = 0; l < MAX_LUN; l++) {
					if ((f = SCGO_FILENO(usalp, b, t, l)) < 0)
						continue;
					old = 0;
					if (ioctl(f, SG_GET_RESERVED_SIZE, &old) < 0)
						continue;
					if (val > old)
						ioctl(f, SG_SET_RESERVED_SIZE, &val);
				}
			}
		}
	}

	/*
	 * To make sure we did not fail (the ioctl does not report errors)
	 * we need to check the DMA limits. We return the smallest value.
	 */
	for (b = 0; b < MAX_SCG; b++) {
		for (t = 0; t < MAX_TGT; t++) {
			for (l = 0; l < MAX_LUN; l++) {
				if ((f = SCGO_FILENO(usalp, b, t, l)) < 0)
					continue;
				if (ioctl(f, SG_GET_RESERVED_SIZE, &val) < 0)
					continue;
				if (usalp->debug > 0) {
					fprintf((FILE *)usalp->errfile,
						"Target (%d,%d,%d): DMA max %d old max: %ld\n",
						b, t, l, val, newmax);
				}
				if (val < newmax)
					newmax = val;
			}
		}
	}
	return ((long)newmax);
}
#endif

static long
usalo_maxdma(SCSI *usalp, long amt)
{
	struct stat stbuf;
	long maxdma = MAX_DMA_LINUX;

#if defined(SG_SET_RESERVED_SIZE) && defined(SG_GET_RESERVED_SIZE)
	/*
	 * Use the curious new kernel interface found on Linux >= 2.2.10
	 * This interface first appeared in 2.2.6 but it was not working.
	 */
	if (usallocal(usalp)->drvers >= 20134)
		maxdma = sg_raisedma(usalp, amt);
#endif
	/*
	 * First try the modern kernel 2.6.1x way to detect the real maximum
	 * DMA for this specific device, then try the other methods.
	 */
	if(0==fstat(usallocal(usalp)->usalfile, &stbuf)) {
		/* that's ugly, there are so many symlinks in sysfs but none from major:minor to the relevant directory */
		static char *basedirs[] = { "/sys/block", "/sys/class/scsi_generic" };
		int i, major, minor;
		char *p;
		char buf[64];
		major=stbuf.st_rdev>>8;
		minor=stbuf.st_rdev&0xFF;
		if (usalp->debug > 0)
			fprintf(stderr, "Looking for data for major:minor: %d:%d\n", major, minor);
		for(i=0;i<2;i++) {
			struct dirent *dent;
			DIR *ddesc = opendir(basedirs[i]);
			if(!ddesc) continue;
			while( NULL != (dent = readdir(ddesc))) { 
				FILE *fd;
				if(dent->d_name[0]=='.')
					continue;
				snprintf(buf, sizeof(buf), "%s/%s/dev", basedirs[i], dent->d_name);
				fd=fopen(buf, "r");
				if(!fd) continue;
				buf[0]='\0';
				fgets(buf, sizeof(buf)-1, fd);
				fclose(fd);
				if( atoi(buf) == major && 
						NULL!=(p=strchr(buf, ':')) && 
						atoi(p) == minor)
				{ /* got the right dir */
					snprintf(buf, sizeof(buf), "%s/%s/queue/max_hw_sectors_kb", basedirs[i], dent->d_name);
					fd=fopen(buf, "r");
					if(!fd) continue;
					buf[0]='\0';
					fgets(buf, sizeof(buf)-1, fd);
					fclose(fd);
					return(512*atoi(buf));
				}
			}
			closedir(ddesc);
		}
	}
#ifdef	SG_GET_BUFSIZE
	/*
	 * We assume that all /dev/sg instances use the same
	 * maximum buffer size.
	 */
	maxdma = ioctl(usallocal(usalp)->usalfile, SG_GET_BUFSIZE, 0);
#endif
	if (maxdma < 0) {
#ifdef	USE_PG
		/*
		 * If we only have a Parallel port, just return PP maxdma.
		 */
		if (usallocal(usalp)->pgbus == 0)
			return (pg_maxdma(usalp, amt));
#endif
		if (usallocal(usalp)->usalfile >= 0)
			maxdma = MAX_DMA_LINUX;
	}
#ifdef	USE_PG
	if (usal_scsibus(usalp) == usallocal(usalp)->pgbus)
		return (pg_maxdma(usalp, amt));
	if ((usal_scsibus(usalp) < 0) && (pg_maxdma(usalp, amt) < maxdma))
		return (pg_maxdma(usalp, amt));
#endif
	return (maxdma);
}

static void *
usalo_getbuf(SCSI *usalp, long amt)
{
	char	*ret;

	if (usalp->debug > 0) {
		fprintf((FILE *)usalp->errfile,
				"usalo_getbuf: %ld bytes\n", amt);
	}
	/*
	 * For performance reason, we allocate pagesize()
	 * bytes before the SCSI buffer to avoid
	 * copying the whole buffer contents when
	 * setting up the /dev/sg data structures.
	 */
	ret = valloc((size_t)(amt+getpagesize()));
	if (ret == NULL)
		return (ret);
	usalp->bufbase = ret;
	ret += getpagesize();
	usallocal(usalp)->SCSIbuf = ret;
	return ((void *)ret);
}

static void
usalo_freebuf(SCSI *usalp)
{
	if (usalp->bufbase)
		free(usalp->bufbase);
	usalp->bufbase = NULL;
}

static BOOL
usalo_havebus(SCSI *usalp, int busno)
{
	register int	t;
	register int	l;

	if (busno < 0 || busno >= MAX_SCG)
		return (FALSE);

	if (usalp->local == NULL)
		return (FALSE);

	for (t = 0; t < MAX_TGT; t++) {
		for (l = 0; l < MAX_LUN; l++)
			if (usallocal(usalp)->usalfiles[busno][t][l] >= 0)
				return (TRUE);
	}
	return (FALSE);
}

static int
usalo_fileno(SCSI *usalp, int busno, int tgt, int tlun)
{
	if (busno < 0 || busno >= MAX_SCG ||
	    tgt < 0 || tgt >= MAX_TGT ||
	    tlun < 0 || tlun >= MAX_LUN)
		return (-1);

	if (usalp->local == NULL)
		return (-1);

	return ((int)usallocal(usalp)->usalfiles[busno][tgt][tlun]);
}

static int
usalo_initiator_id(SCSI *usalp)
{
#ifdef	USE_PG
	if (usal_scsibus(usalp) == usallocal(usalp)->pgbus)
		return (pg_initiator_id(usalp));
#endif
	return (-1);
}

static int
usalo_isatapi(SCSI *usalp)
{
#ifdef	USE_PG
	if (usal_scsibus(usalp) == usallocal(usalp)->pgbus)
		return (pg_isatapi(usalp));
#endif

	/*
	 * The /dev/hd* interface always returns TRUE for SG_EMULATED_HOST.
	 * So this is completely useless.
	 */
	if (usallocal(usalp)->flags & LF_ATA)
		return (-1);

#ifdef	SG_EMULATED_HOST
	{
	int	emulated = FALSE;

	/*
	 * XXX Should we use this at all?
	 * XXX The badly designed /dev/hd* interface always
	 * XXX returns TRUE, even when used with e.g. /dev/sr0.
	 */
	if (ioctl(usalp->fd, SG_EMULATED_HOST, &emulated) >= 0)
		return (emulated != 0);
	}
#endif
	return (-1);
}

static int
usalo_reset(SCSI *usalp, int what)
{
#ifdef	SG_SCSI_RESET
	int	f = usalp->fd;
	int	func = -1;
#endif
#ifdef	USE_PG
	if (usal_scsibus(usalp) == usallocal(usalp)->pgbus)
		return (pg_reset(usalp, what));
#endif
	/*
	 * Do we have a SCSI reset in the Linux sg driver?
	 */
#ifdef	SG_SCSI_RESET
	/*
	 * Newer Linux sg driver seem to finally implement it...
	 */
#ifdef	SG_SCSI_RESET_NOTHING
	func = SG_SCSI_RESET_NOTHING;
	if (ioctl(f, SG_SCSI_RESET, &func) >= 0) {
		if (what == SCG_RESET_NOP)
			return (0);
#ifdef	SG_SCSI_RESET_DEVICE
		if (what == SCG_RESET_TGT) {
			func = SG_SCSI_RESET_DEVICE;
			if (ioctl(f, SG_SCSI_RESET, &func) >= 0)
				return (0);
		}
#endif
#ifdef	SG_SCSI_RESET_BUS
		if (what == SCG_RESET_BUS) {
			func = SG_SCSI_RESET_BUS;
			if (ioctl(f, SG_SCSI_RESET, &func) >= 0)
				return (0);
		}
#endif
	}
#endif
#endif
	return (-1);
}

static void
sg_settimeout(int f, int tmo)
{
#ifndef HZ
	static int HZ=0;
	if (!HZ)
		HZ = sysconf(_SC_CLK_TCK);
#endif
	tmo *= HZ;
	if (tmo)
		tmo += HZ/2;

	if (ioctl(f, SG_SET_TIMEOUT, &tmo) < 0)
		comerr("Cannot set SG_SET_TIMEOUT.\n");
}

/*
 * Get misaligned int.
 * Needed for all recent processors (sparc/ppc/alpha)
 * because the /dev/sg design forces us to do misaligned
 * reads of integers.
 */
#ifdef	MISALIGN
static int
sg_getint(int *ip)
{
		int	ret;
	register char	*cp = (char *)ip;
	register char	*tp = (char *)&ret;
	register int	i;

	for (i = sizeof (int); --i >= 0; )
		*tp++ = *cp++;

	return (ret);
}
#define	GETINT(a)	sg_getint(&(a))
#else
#define	GETINT(a)	(a)
#endif

#ifdef	SG_IO
static int
usalo_send(SCSI *usalp)
{
	struct usal_cmd	*sp = usalp->scmd;
	int		ret;
	sg_io_hdr_t	sg_io;
	struct timeval	to;

	if (usalp->fd < 0) {
		sp->error = SCG_FATAL;
		sp->ux_errno = EIO;
		return (0);
	}
	if (usallocal(usalp)->isold > 0) {
		return (sg_rwsend(usalp));
	}
	fillbytes((caddr_t)&sg_io, sizeof (sg_io), '\0');

	sg_io.interface_id = 'S';

	if (sp->flags & SCG_RECV_DATA) {
		sg_io.dxfer_direction = SG_DXFER_FROM_DEV;
	} else if (sp->size > 0) {
		sg_io.dxfer_direction = SG_DXFER_TO_DEV;
	} else {
		sg_io.dxfer_direction = SG_DXFER_NONE;
	}
	sg_io.cmd_len = sp->cdb_len;
	if (sp->sense_len > SG_MAX_SENSE)
		sg_io.mx_sb_len = SG_MAX_SENSE;
	else
		sg_io.mx_sb_len = sp->sense_len;
	sg_io.dxfer_len = sp->size;
	sg_io.dxferp = sp->addr;
	sg_io.cmdp = sp->cdb.cmd_cdb;
	sg_io.sbp = sp->u_sense.cmd_sense;
	sg_io.timeout = sp->timeout*1000;
	sg_io.flags |= SG_FLAG_DIRECT_IO;

	ret = ioctl(usalp->fd, SG_IO, &sg_io);
	if (usalp->debug > 0) {
		fprintf((FILE *)usalp->errfile,
				"ioctl ret: %d\n", ret);
	}

	if (ret < 0) {
		sp->ux_errno = geterrno();
		/*
		 * Check if SCSI command cound not be send at all.
		 * Linux usually returns EINVAL for an unknoen ioctl.
		 * In case somebody from the Linux kernel team learns that the
		 * corect errno would be ENOTTY, we check for this errno too.
		 */
		if ((sp->ux_errno == ENOTTY || sp->ux_errno == EINVAL) &&
		    usallocal(usalp)->isold < 0) {
			usallocal(usalp)->isold = 1;
			return (sg_rwsend(usalp));
		}
		if (sp->ux_errno == ENXIO ||
		    sp->ux_errno == EINVAL || sp->ux_errno == EACCES) {
			return (-1);
		}
	}

	sp->u_scb.cmd_scb[0] = sg_io.status;
	sp->sense_count = sg_io.sb_len_wr;

	if (usalp->debug > 0) {
		fprintf((FILE *)usalp->errfile,
				"host_status: %02X driver_status: %02X\n",
				sg_io.host_status, sg_io.driver_status);
	}

	switch (sg_io.host_status) {

	case DID_OK:
			/*
			 * If there is no DMA overrun and there is a
			 * SCSI Status byte != 0 then the SCSI cdb transport
			 * was OK and sp->error must be SCG_NO_ERROR.
			 */
			if ((sg_io.driver_status & DRIVER_SENSE) != 0) {
				if (sp->ux_errno == 0)
					sp->ux_errno = EIO;

				if (sp->u_sense.cmd_sense[0] != 0 &&
				    sp->u_scb.cmd_scb[0] == 0) {
					/*
					 * The Linux SCSI system up to 2.4.xx
					 * trashes the status byte in the
					 * kernel. This is true at least for
					 * ide-scsi emulation. Until this gets
					 * fixed, we need this hack.
					 */
					sp->u_scb.cmd_scb[0] = ST_CHK_COND;
					if (sp->sense_count == 0)
						sp->sense_count = SG_MAX_SENSE;

					if ((sp->u_sense.cmd_sense[2] == 0) &&
					    (sp->u_sense.cmd_sense[12] == 0) &&
					    (sp->u_sense.cmd_sense[13] == 0)) {
						/*
						 * The Linux SCSI system will
						 * send a request sense for
						 * even a dma underrun error.
						 * Clear CHECK CONDITION state
						 * in case of No Sense.
						 */
						sp->u_scb.cmd_scb[0] = 0;
						sp->u_sense.cmd_sense[0] = 0;
						sp->sense_count = 0;
					}
				}
			}
			break;

	case DID_NO_CONNECT:	/* Arbitration won, retry NO_CONNECT? */
			sp->error = SCG_RETRYABLE;
			break;
	case DID_BAD_TARGET:
			sp->error = SCG_FATAL;
			break;

	case DID_TIME_OUT:
		__usal_times(usalp);

		if (sp->timeout > 1 && usalp->cmdstop->tv_sec == 0) {
			sp->u_scb.cmd_scb[0] = 0;
			sp->error = SCG_FATAL;	/* a selection timeout */
		} else {
			sp->error = SCG_TIMEOUT;
		}
		break;

	default:
		to.tv_sec = sp->timeout;
		to.tv_usec = 500000;
		__usal_times(usalp);

		if (usalp->cmdstop->tv_sec < to.tv_sec ||
		    (usalp->cmdstop->tv_sec == to.tv_sec &&
			usalp->cmdstop->tv_usec < to.tv_usec)) {

			sp->ux_errno = 0;
			sp->error = SCG_TIMEOUT;	/* a timeout */
		} else {
			sp->error = SCG_RETRYABLE;
		}
		break;
	}
	if (sp->error && sp->ux_errno == 0)
		sp->ux_errno = EIO;

	sp->resid = sg_io.resid;
	return (0);
}
#else
#	define	sg_rwsend	usalo_send
#endif

static int
sg_rwsend(SCSI *usalp)
{
	int		f = usalp->fd;
	struct usal_cmd	*sp = usalp->scmd;
	struct sg_rq	*sgp;
	struct sg_rq	*sgp2;
	int	i;
	int	pack_len;
	int	reply_len;
	int	amt = sp->cdb_len;
	struct sg_rq {
		struct sg_header	hd;
		unsigned char		buf[MAX_DMA_LINUX+SCG_MAX_CMD];
	} sg_rq;
#ifdef	SG_GET_BUFSIZE		/* We may use a 'sg' version 2 driver	*/
	char	driver_byte;
	char	host_byte;
	char	msg_byte;
	char	status_byte;
#endif

	if (f < 0) {
		sp->error = SCG_FATAL;
		sp->ux_errno = EIO;
		return (0);
	}
#ifdef	USE_PG
	if (usal_scsibus(usalp) == usallocal(usalp)->pgbus)
		return (pg_send(usalp));
#endif
	if (sp->timeout != usalp->deftimeout)
		sg_settimeout(f, sp->timeout);


	sgp2 = sgp = &sg_rq;
	if (sp->addr == usallocal(usalp)->SCSIbuf) {
		sgp = (struct sg_rq *)
			(usallocal(usalp)->SCSIbuf - (sizeof (struct sg_header) + amt));
		sgp2 = (struct sg_rq *)
			(usallocal(usalp)->SCSIbuf - (sizeof (struct sg_header)));
	} else {
		if (usalp->debug > 0) {
			fprintf((FILE *)usalp->errfile,
				"DMA addr: 0x%8.8lX size: %d - using copy buffer\n",
				(long)sp->addr, sp->size);
		}
		if (sp->size > (int)(sizeof (sg_rq.buf) - SCG_MAX_CMD)) {

			if (usallocal(usalp)->xbuf == NULL) {
				usallocal(usalp)->xbufsize = usalp->maxbuf;
				usallocal(usalp)->xbuf =
					malloc(usallocal(usalp)->xbufsize +
						SCG_MAX_CMD +
						sizeof (struct sg_header));
				if (usalp->debug > 0) {
					fprintf((FILE *)usalp->errfile,
						"Allocated DMA copy buffer, addr: 0x%8.8lX size: %ld\n",
						(long)usallocal(usalp)->xbuf,
						usalp->maxbuf);
				}
			}
			if (usallocal(usalp)->xbuf == NULL ||
				sp->size > usallocal(usalp)->xbufsize) {
				errno = ENOMEM;
				return (-1);
			}
			sgp2 = sgp = (struct sg_rq *)usallocal(usalp)->xbuf;
		}
	}

	/*
	 * This is done to avoid misaligned access of sgp->some_int
	 */
	pack_len = sizeof (struct sg_header) + amt;
	reply_len = sizeof (struct sg_header);
	if (sp->flags & SCG_RECV_DATA) {
		reply_len += sp->size;
	} else {
		pack_len += sp->size;
	}

#ifdef	MISALIGN
	/*
	 * sgp->some_int may be misaligned if (sp->addr == SCSIbuf)
	 * This is no problem on Intel porocessors, however
	 * all other processors don't like it.
	 * sizeof (struct sg_header) + amt is usually not a multiple of
	 * sizeof (int). For this reason, we fill in the values into sg_rq
	 * which is always corectly aligned and then copy it to the real
	 * location if this location differs from sg_rq.
	 * Never read/write directly to sgp->some_int !!!!!
	 */
	fillbytes((caddr_t)&sg_rq, sizeof (struct sg_header), '\0');

	sg_rq.hd.pack_len = pack_len;
	sg_rq.hd.reply_len = reply_len;
	sg_rq.hd.pack_id = usallocal(usalp)->pack_id++;
/*	sg_rq.hd.result = 0;	not needed because of fillbytes() */

	if ((caddr_t)&sg_rq != (caddr_t)sgp)
		movebytes((caddr_t)&sg_rq, (caddr_t)sgp, sizeof (struct sg_header));
#else
	fillbytes((caddr_t)sgp, sizeof (struct sg_header), '\0');

	sgp->hd.pack_len = pack_len;
	sgp->hd.reply_len = reply_len;
	sgp->hd.pack_id = usallocal(usalp)->pack_id++;
/*	sgp->hd.result = 0;	not needed because of fillbytes() */
#endif
	if (amt == 12)
		sgp->hd.twelve_byte = 1;


	for (i = 0; i < amt; i++) {
		sgp->buf[i] = sp->cdb.cmd_cdb[i];
	}
	if (!(sp->flags & SCG_RECV_DATA)) {
		if ((void *)sp->addr != (void *)&sgp->buf[amt])
			movebytes(sp->addr, &sgp->buf[amt], sp->size);
		amt += sp->size;
	}
#ifdef	SG_GET_BUFSIZE
	sgp->hd.want_new  = 1;			/* Order new behaviour	*/
	sgp->hd.cdb_len	  = sp->cdb_len;	/* Set CDB length	*/
	if (sp->sense_len > SG_MAX_SENSE)
		sgp->hd.sense_len = SG_MAX_SENSE;
	else
		sgp->hd.sense_len = sp->sense_len;
#endif
	i = sizeof (struct sg_header) + amt;
	if ((amt = write(f, sgp, i)) < 0) {			/* write */
		sg_settimeout(f, usalp->deftimeout);
		return (-1);
	} else if (amt != i) {
		errmsg("usalo_send(%s) wrote %d bytes (expected %d).\n",
						usalp->cmdname, amt, i);
	}

	if (sp->addr == usallocal(usalp)->SCSIbuf) {
		movebytes(sgp, sgp2, sizeof (struct sg_header));
		sgp = sgp2;
	}
	sgp->hd.sense_buffer[0] = 0;
	if ((amt = read(f, sgp, reply_len)) < 0) {		/* read */
		sg_settimeout(f, usalp->deftimeout);
		return (-1);
	}

	if (sp->flags & SCG_RECV_DATA && ((void *)sgp->buf != (void *)sp->addr)) {
		movebytes(sgp->buf, sp->addr, sp->size);
	}
	sp->ux_errno = GETINT(sgp->hd.result);		/* Unaligned read */
	sp->error = SCG_NO_ERROR;

#ifdef	SG_GET_BUFSIZE
	if (sgp->hd.grant_new) {
		sp->sense_count = sgp->hd.sense_len;
		pack_len    = GETINT(sgp->hd.sg_cmd_status);	/* Unaligned read */
		driver_byte = (pack_len  >> 24) & 0xFF;
		host_byte   = (pack_len  >> 16) & 0xFF;
		msg_byte    = (pack_len  >> 8) & 0xFF;
		status_byte =  pack_len  & 0xFF;

		switch (host_byte) {

		case DID_OK:
				if ((driver_byte & DRIVER_SENSE ||
				    sgp->hd.sense_buffer[0] != 0) &&
				    status_byte == 0) {
					/*
					 * The Linux SCSI system up to 2.4.xx
					 * trashes the status byte in the
					 * kernel. This is true at least for
					 * ide-scsi emulation. Until this gets
					 * fixed, we need this hack.
					 */
					status_byte = ST_CHK_COND;
					if (sgp->hd.sense_len == 0)
						sgp->hd.sense_len = SG_MAX_SENSE;

					if ((sp->u_sense.cmd_sense[2] == 0) &&
					    (sp->u_sense.cmd_sense[12] == 0) &&
					    (sp->u_sense.cmd_sense[13] == 0)) {
						/*
						 * The Linux SCSI system will
						 * send a request sense for
						 * even a dma underrun error.
						 * Clear CHECK CONDITION state
						 * in case of No Sense.
						 */
						sp->u_scb.cmd_scb[0] = 0;
						sp->u_sense.cmd_sense[0] = 0;
						sp->sense_count = 0;
					}
				}
				break;

		case DID_NO_CONNECT:	/* Arbitration won, retry NO_CONNECT? */
				sp->error = SCG_RETRYABLE;
				break;

		case DID_BAD_TARGET:
				sp->error = SCG_FATAL;
				break;

		case DID_TIME_OUT:
				sp->error = SCG_TIMEOUT;
				break;

		default:
				sp->error = SCG_RETRYABLE;

				if ((driver_byte & DRIVER_SENSE ||
				    sgp->hd.sense_buffer[0] != 0) &&
				    status_byte == 0) {
					status_byte = ST_CHK_COND;
					sp->error = SCG_NO_ERROR;
				}
				if (status_byte != 0 && sgp->hd.sense_len == 0) {
					sgp->hd.sense_len = SG_MAX_SENSE;
					sp->error = SCG_NO_ERROR;
				}
				break;

		}
		if ((host_byte != DID_OK || status_byte != 0) && sp->ux_errno == 0)
			sp->ux_errno = EIO;
		sp->u_scb.cmd_scb[0] = status_byte;
		if (status_byte & ST_CHK_COND) {
			sp->sense_count = sgp->hd.sense_len;
			movebytes(sgp->hd.sense_buffer, sp->u_sense.cmd_sense, sp->sense_count);
		}
	} else
#endif
	{
		if (GETINT(sgp->hd.result) == EBUSY) {	/* Unaligned read */
			struct timeval to;

			to.tv_sec = sp->timeout;
			to.tv_usec = 500000;
			__usal_times(usalp);

			if (sp->timeout > 1 && usalp->cmdstop->tv_sec == 0) {
				sp->u_scb.cmd_scb[0] = 0;
				sp->ux_errno = EIO;
				sp->error = SCG_FATAL;	/* a selection timeout */
			} else if (usalp->cmdstop->tv_sec < to.tv_sec ||
			    (usalp->cmdstop->tv_sec == to.tv_sec &&
				usalp->cmdstop->tv_usec < to.tv_usec)) {

				sp->ux_errno = EIO;
				sp->error = SCG_TIMEOUT;	/* a timeout */
			} else {
				sp->error = SCG_RETRYABLE;	/* may be BUS_BUSY */
			}
		}

		if (sp->flags & SCG_RECV_DATA)
			sp->resid = (sp->size + sizeof (struct sg_header)) - amt;
		else
			sp->resid = 0;	/* sg version1 cannot return DMA resid count */

		if (sgp->hd.sense_buffer[0] != 0) {
			sp->scb.chk = 1;
			sp->sense_count = SG_MAX_SENSE;
			movebytes(sgp->hd.sense_buffer, sp->u_sense.cmd_sense, sp->sense_count);
			if (sp->ux_errno == 0)
				sp->ux_errno = EIO;
		}
	}

	if (usalp->verbose > 0 && usalp->debug > 0) {
#ifdef	SG_GET_BUFSIZE
		fprintf((FILE *)usalp->errfile,
				"status: 0x%08X pack_len: %d, reply_len: %d pack_id: %d result: %d wn: %d gn: %d cdb_len: %d sense_len: %d sense[0]: %02X\n",
				GETINT(sgp->hd.sg_cmd_status),
				GETINT(sgp->hd.pack_len),
				GETINT(sgp->hd.reply_len),
				GETINT(sgp->hd.pack_id),
				GETINT(sgp->hd.result),
				sgp->hd.want_new,
				sgp->hd.grant_new,
				sgp->hd.cdb_len,
				sgp->hd.sense_len,
				sgp->hd.sense_buffer[0]);
#else
		fprintf((FILE *)usalp->errfile,
				"pack_len: %d, reply_len: %d pack_id: %d result: %d sense[0]: %02X\n",
				GETINT(sgp->hd.pack_len),
				GETINT(sgp->hd.reply_len),
				GETINT(sgp->hd.pack_id),
				GETINT(sgp->hd.result),
				sgp->hd.sense_buffer[0]);
#endif
#ifdef	DEBUG
		fprintf((FILE *)usalp->errfile, "sense: ");
		for (i = 0; i < 16; i++)
			fprintf((FILE *)usalp->errfile, "%02X ", sgp->hd.sense_buffer[i]);
		fprintf((FILE *)usalp->errfile, "\n");
#endif
	}

	if (sp->timeout != usalp->deftimeout)
		sg_settimeout(f, usalp->deftimeout);
	return (0);
}

