/* @(#)scsi-sun.c	1.87 07/07/07 Copyright 1988,1995,2000-2004 J. Schilling */
#ifndef lint
static	char __sccsid[] =
	"@(#)scsi-sun.c	1.87 07/07/07 Copyright 1988,1995,2000-2004 J. Schilling";
#endif
/*
 *	SCSI user level command transport routines for
 *	the SCSI general driver 'scg'.
 *
 *	Warning: you may change this source, but if you do that
 *	you need to change the _scg_version and _scg_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) 1988,1995,2000-2004 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * The following exceptions apply:
 * CDDL §3.6 needs to be replaced by: "You may create a Larger Work by
 * combining Covered Software with other code if all other code is governed by
 * the terms of a license that is OSI approved (see www.opensource.org) and
 * you may distribute the Larger Work as a single product. In such a case,
 * You must make sure the requirements of this License are fulfilled for
 * the Covered Software."
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#include <scg/scgio.h>

#include <schily/libport.h>	/* Needed for gethostid() */
#ifdef	HAVE_SUN_DKIO_H
#	include <sun/dkio.h>

#	define	dk_cinfo	dk_conf
#	define	dki_slave	dkc_slave
#	define	DKIO_GETCINFO	DKIOCGCONF
#endif
#ifdef	HAVE_SYS_DKIO_H
#	include <sys/dkio.h>

#	define	DKIO_GETCINFO	DKIOCINFO
#endif

#define	TARGET(slave)	((slave) >> 3)
#define	LUN(slave)	((slave) & 07)

/*
 * Tht USCSI ioctl() is not usable on SunOS 4.x
 */
#ifdef	__SVR4
/*#define	VOLMGT_DEBUG*/
#include <volmgt.h>
#include <schily/stat.h>
#	define	USE_USCSI
#endif

LOCAL	char	_scg_trans_version[] = "scg-1.87";	/* The version for /dev/scg	*/
LOCAL	char	_scg_utrans_version[] = "uscsi-1.87";	/* The version for USCSI	*/

#ifdef	USE_USCSI
LOCAL	int	scgo_uhelp	__PR((SCSI *scgp, FILE *f));
LOCAL	int	scgo_uopen	__PR((SCSI *scgp, char *device));
LOCAL	int	scgo_volopen	__PR((SCSI *scgp, char *devname));
LOCAL	int	scgo_openmedia	__PR((SCSI *scgp, char *mname));
LOCAL	int	scgo_uclose	__PR((SCSI *scgp));
LOCAL	int	scgo_ucinfo	__PR((int f, struct dk_cinfo *cp, int debug));
LOCAL	int	scgo_ugettlun	__PR((int f, int *tgtp, int *lunp));
LOCAL	long	scgo_umaxdma	__PR((SCSI *scgp, long amt));
LOCAL	int	scgo_openide	__PR((void));
LOCAL	int	scgo_unumbus	__PR((SCSI *scgp));
LOCAL	BOOL	scgo_uhavebus	__PR((SCSI *scgp, int));
LOCAL	int	scgo_ufileno	__PR((SCSI *scgp, int, int, int));
LOCAL	int	scgo_uinitiator_id __PR((SCSI *scgp));
LOCAL	int	scgo_uisatapi	__PR((SCSI *scgp));
LOCAL	int	scgo_ureset	__PR((SCSI *scgp, int what));
LOCAL	int	scgo_usend	__PR((SCSI *scgp));

LOCAL	int	have_volmgt = -1;

LOCAL scg_ops_t sun_uscsi_ops = {
	scgo_usend,
	scgo_version,		/* Shared with SCG driver */
	scgo_uhelp,
	scgo_uopen,
	scgo_uclose,
	scgo_umaxdma,
	scgo_getbuf,		/* Shared with SCG driver */
	scgo_freebuf,		/* Shared with SCG driver */
	scgo_unumbus,
	scgo_uhavebus,
	scgo_ufileno,
	scgo_uinitiator_id,
	scgo_uisatapi,
	scgo_ureset,
};
#endif

/*
 * Need to move this into an scg driver ioctl.
 */
/*#define	MAX_DMA_SUN4M	(1024*1024)*/
#define	MAX_DMA_SUN4M	(124*1024)	/* Currently max working size */
/*#define	MAX_DMA_SUN4C	(126*1024)*/
#define	MAX_DMA_SUN4C	(124*1024)	/* Currently max working size */
#define	MAX_DMA_SUN3	(63*1024)
#define	MAX_DMA_SUN386	(56*1024)
#define	MAX_DMA_OTHER	(32*1024)

#define	ARCH_MASK	0xF0
#define	ARCH_SUN2	0x00
#define	ARCH_SUN3	0x10
#define	ARCH_SUN4	0x20
#define	ARCH_SUN386	0x30
#define	ARCH_SUN3X	0x40
#define	ARCH_SUN4C	0x50
#define	ARCH_SUN4E	0x60
#define	ARCH_SUN4M	0x70
#define	ARCH_SUNX	0x80

/*
 * We are using a "real" /dev/scg?
 */
#define	scsi_xsend(scgp)	ioctl((scgp)->fd, SCGIO_CMD, (scgp)->scmd)
#define	MAX_SCG		16	/* Max # of SCSI controllers */
#define	MAX_TGT		16
#define	MAX_LUN		8

struct scg_local {
	union {
		int	SCG_files[MAX_SCG];
#ifdef	USE_USCSI
		short	scg_files[MAX_SCG][MAX_TGT][MAX_LUN];
#endif
	} u;
};
#define	scglocal(p)	((struct scg_local *)((p)->local))
#define	scgfiles(p)	(scglocal(p)->u.SCG_files)

/*
 * Return version information for the low level SCSI transport code.
 * This has been introduced to make it easier to trace down problems
 * in applications.
 */
LOCAL char *
scgo_version(scgp, what)
	SCSI	*scgp;
	int	what;
{
	if (scgp != (SCSI *)0) {
		switch (what) {

		case SCG_VERSION:
#ifdef	USE_USCSI
			if (scgp->ops == &sun_uscsi_ops)
				return (_scg_utrans_version);
#endif
			return (_scg_trans_version);
		/*
		 * If you changed this source, you are not allowed to
		 * return "schily" for the SCG_AUTHOR request.
		 */
		case SCG_AUTHOR:
			return (_scg_auth_schily);
		case SCG_SCCS_ID:
			return (__sccsid);
		}
	}
	return ((char *)0);
}

LOCAL int
scgo_help(scgp, f)
	SCSI	*scgp;
	FILE	*f;
{
	__scg_help(f, "scg", "Generic transport independent SCSI",
		"", "bus,target,lun", "1,2,0", TRUE, FALSE);
#ifdef	USE_USCSI
	scgo_uhelp(scgp, f);
#endif
	return (0);
}

LOCAL int
scgo_open(scgp, device)
	SCSI	*scgp;
	char	*device;
{
		int	busno	= scg_scsibus(scgp);
		int	tgt	= scg_target(scgp);
/*		int	tlun	= scg_lun(scgp);*/
	register int	f;
	register int	i;
	register int	nopen = 0;
	char		devname[32];

	if (busno >= MAX_SCG) {
		errno = EINVAL;
		if (scgp->errstr)
			js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
				"Illegal value for busno '%d'", busno);
		return (-1);
	}

	if ((device != NULL && *device != '\0') || (busno == -2 && tgt == -2)) {
#ifdef	USE_USCSI
		scgp->ops = &sun_uscsi_ops;
		return (SCGO_OPEN(scgp, device));
#else
		errno = EINVAL;
		if (scgp->errstr)
			js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
				"Open by 'devname' not supported on this OS");
		return (-1);
#endif
	}

	if (scgp->local == NULL) {
		scgp->local = malloc(sizeof (struct scg_local));
		if (scgp->local == NULL) {
			if (scgp->errstr)
				js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE, "No memory for scg_local");
			return (0);
		}

		for (i = 0; i < MAX_SCG; i++) {
			scgfiles(scgp)[i] = -1;
		}
	}


	for (i = 0; i < MAX_SCG; i++) {
		/*
		 * Skip unneeded devices if not in SCSI Bus scan open mode
		 */
		if (busno >= 0 && busno != i)
			continue;
		js_snprintf(devname, sizeof (devname), "/dev/scg%d", i);
		f = open(devname, O_RDWR);
		if (f < 0) {
			if (errno != ENOENT && errno != ENXIO) {
				if (scgp->errstr)
					js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
						"Cannot open '%s'", devname);
				return (-1);
			}
		} else {
			nopen++;
		}
		scgfiles(scgp)[i] = f;
	}
#ifdef	USE_USCSI
	if (nopen <= 0) {
		if (scgp->local != NULL) {
			free(scgp->local);
			scgp->local = NULL;
		}
		scgp->ops = &sun_uscsi_ops;
		return (SCGO_OPEN(scgp, device));
	}
#endif
	return (nopen);
}

LOCAL int
scgo_close(scgp)
	SCSI	*scgp;
{
	register int	i;

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

	for (i = 0; i < MAX_SCG; i++) {
		if (scgfiles(scgp)[i] >= 0)
			close(scgfiles(scgp)[i]);
		scgfiles(scgp)[i] = -1;
	}
	return (0);
}

LOCAL long
scgo_maxdma(scgp, amt)
	SCSI	*scgp;
	long	amt;
{
	long	maxdma = MAX_DMA_OTHER;
#if	!defined(__i386_) && !defined(i386)
	int	cpu_type;
#endif

#if	defined(__i386_) || defined(i386)
	maxdma = MAX_DMA_SUN386;
#else
	cpu_type = gethostid() >> 24;

	switch (cpu_type & ARCH_MASK) {

	case ARCH_SUN4C:
	case ARCH_SUN4E:
		maxdma = MAX_DMA_SUN4C;
		break;

	case ARCH_SUN4M:
	case ARCH_SUNX:
		maxdma = MAX_DMA_SUN4M;
		break;

	default:
		maxdma = MAX_DMA_SUN3;
	}
#endif

#ifndef	__SVR4
	/*
	 * SunOS 4.x allows esp hardware on VME boards and thus
	 * limits DMA on esp to 64k-1
	 */
	if (maxdma > MAX_DMA_SUN3)
		maxdma = MAX_DMA_SUN3;
#endif
	return (maxdma);
}

LOCAL int
scgo_numbus(scgp)
	SCSI	*scgp;
{
	return (MAX_SCG);
}

LOCAL BOOL
scgo_havebus(scgp, busno)
	SCSI	*scgp;
	int	busno;
{
	if (scgp->local == NULL)
		return (FALSE);

	return (busno < 0 || busno >= MAX_SCG) ? FALSE : (scgfiles(scgp)[busno] >= 0);
}

LOCAL int
scgo_fileno(scgp, busno, tgt, tlun)
	SCSI	*scgp;
	int	busno;
	int	tgt;
	int	tlun;
{
	if (scgp->local == NULL)
		return (-1);

	return ((busno < 0 || busno >= MAX_SCG) ? -1 : scgfiles(scgp)[busno]);
}

LOCAL int
scgo_initiator_id(scgp)
	SCSI	*scgp;
{
	int		id = -1;
#ifdef	DKIO_GETCINFO
	struct dk_cinfo	conf;
#endif

#ifdef	DKIO_GETCINFO
	if (scgp->fd < 0)
		return (id);
	if (ioctl(scgp->fd, DKIO_GETCINFO, &conf) < 0)
		return (id);
	if (TARGET(conf.dki_slave) != -1)
		id = TARGET(conf.dki_slave);
#endif
	return (id);
}

LOCAL int
scgo_isatapi(scgp)
	SCSI	*scgp;
{
	return (FALSE);
}

LOCAL int
scgo_reset(scgp, what)
	SCSI	*scgp;
	int	what;
{
	if (what == SCG_RESET_NOP)
		return (0);
	if (what != SCG_RESET_BUS) {
		errno = EINVAL;
		return (-1);
	}
	return (ioctl(scgp->fd, SCGIORESET, 0));
}

LOCAL void *
scgo_getbuf(scgp, amt)
	SCSI	*scgp;
	long	amt;
{
	scgp->bufbase = (void *)valloc((size_t)amt);
	return (scgp->bufbase);
}

LOCAL void
scgo_freebuf(scgp)
	SCSI	*scgp;
{
	if (scgp->bufbase)
		free(scgp->bufbase);
	scgp->bufbase = NULL;
}

LOCAL int
scgo_send(scgp)
	SCSI	*scgp;
{
	scgp->scmd->target = scg_target(scgp);
	return (ioctl(scgp->fd, SCGIO_CMD, scgp->scmd));
}

/*--------------------------------------------------------------------------*/
/*
 *	This is Sun USCSI interface code ...
 */
#ifdef	USE_USCSI
#include <sys/scsi/impl/uscsi.h>

/*
 * Bit Mask definitions, for use accessing the status as a byte.
 */
#define	STATUS_MASK			0x3E
#define	STATUS_GOOD			0x00
#define	STATUS_CHECK			0x02

#define	STATUS_RESERVATION_CONFLICT	0x18
#define	STATUS_TERMINATED		0x22

#ifdef	nonono
#define	STATUS_MASK			0x3E
#define	STATUS_GOOD			0x00
#define	STATUS_CHECK			0x02

#define	STATUS_MET			0x04
#define	STATUS_BUSY			0x08
#define	STATUS_INTERMEDIATE		0x10
#define	STATUS_SCSI2			0x20
#define	STATUS_INTERMEDIATE_MET		0x14
#define	STATUS_RESERVATION_CONFLICT	0x18
#define	STATUS_TERMINATED		0x22
#define	STATUS_QFULL			0x28
#define	STATUS_ACA_ACTIVE		0x30
#endif

LOCAL int
scgo_uhelp(scgp, f)
	SCSI	*scgp;
	FILE	*f;
{
	__scg_help(f, "USCSI", "SCSI transport for targets known by Solaris drivers",
		"USCSI:", "bus,target,lun", "USCSI:1,2,0", TRUE, TRUE);
	return (0);
}

LOCAL int
scgo_uopen(scgp, device)
	SCSI	*scgp;
	char	*device;
{
		int	busno	= scg_scsibus(scgp);
		int	tgt	= scg_target(scgp);
		int	tlun	= scg_lun(scgp);
	register int	f;
	register int	b;
	register int	t;
	register int	l;
	register int	nopen = 0;
	char		devname[32];

	if (have_volmgt < 0)
		have_volmgt = volmgt_running();

	if (scgp->overbose) {
		js_fprintf((FILE *)scgp->errfile,
				"Warning: Using USCSI interface.\n");
	}
#ifndef	SEEK_HOLE
	/*
	 * SEEK_HOLE first appears in Solaris 11 Build 14, volmgt supports
	 * medialess drives since Build 21. Using SEEK_HOLD as indicator
	 * seems to be the best guess.
	 */
	if (scgp->overbose > 0 && have_volmgt) {
		js_fprintf((FILE *)scgp->errfile,
		"Warning: Volume management is running, medialess managed drives are invisible.\n");
	}
#endif

	if (busno >= MAX_SCG || tgt >= MAX_TGT || tlun >= MAX_LUN) {
		errno = EINVAL;
		if (scgp->errstr) {
			js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
				"Illegal value for busno, target or lun '%d,%d,%d'",
					busno, tgt, tlun);
		}
		return (-1);
	}
	if (scgp->local == NULL) {
		scgp->local = malloc(sizeof (struct scg_local));
		if (scgp->local == NULL) {
			if (scgp->errstr)
				js_snprintf(scgp->errstr, SCSI_ERRSTR_SIZE, "No memory for scg_local");
			return (0);
		}

		for (b = 0; b < MAX_SCG; b++) {
			for (t = 0; t < MAX_TGT; t++) {
				for (l = 0; l < MAX_LUN; l++)
					scglocal(scgp)->u.scg_files[b][t][l] = (short)-1;
			}
		}
	}

	if (device != NULL && strcmp(device, "USCSI") == 0)
		goto uscsiscan;

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

uscsiscan:
	if (busno >= 0 && tgt >= 0 && tlun >= 0) {

		if (busno >= MAX_SCG || tgt >= MAX_TGT || tlun >= MAX_LUN)
			return (-1);

		js_snprintf(devname, sizeof (devname), "/dev/rdsk/c%dt%dd%ds2",
			busno, tgt, tlun);
		f = open(devname, O_RDONLY | O_NDELAY);
		if (f < 0 && geterrno() == EBUSY)
			f = scgo_volopen(scgp, devname);
		if (f < 0) {
			js_snprintf(scgp->errstr,
				    SCSI_ERRSTR_SIZE,
				"Cannot open '%s'", devname);
			return (0);
		}
		scglocal(scgp)->u.scg_files[busno][tgt][tlun] = f;
		return (1);
	} else {
		int	errsav = 0;

		for (b = 0; b < MAX_SCG; b++) {
			for (t = 0; t < MAX_TGT; t++) {
				for (l = 0; l < MAX_LUN; l++) {
					js_snprintf(devname, sizeof (devname),
						"/dev/rdsk/c%dt%dd%ds2",
						b, t, l);
					f = open(devname, O_RDONLY | O_NDELAY);
					if (f < 0 && geterrno() == EBUSY) {
						f = scgo_volopen(scgp, devname);
						/*
						 * Hack to mark inaccessible
						 * drives with fd == -2
						 */
						if (f < 0 &&
						    scglocal(scgp)->u.scg_files[b][t][l] < 0)
							scglocal(scgp)->u.scg_files[b][t][l] = f;
					}
					if (f < 0 && errno != ENOENT &&
						    errno != ENXIO &&
						    errno != ENODEV) {
						errsav = geterrno();
						if (scgp->errstr)
							js_snprintf(scgp->errstr,
							    SCSI_ERRSTR_SIZE,
							    "Cannot open '%s'", devname);
					}
					if (f < 0 && l == 0)
						break;
					if (f >= 0) {
						nopen ++;
						if (scglocal(scgp)->u.scg_files[b][t][l] == -1)
							scglocal(scgp)->u.scg_files[b][t][l] = f;
						else
							close(f);
					}
				}
			}
		}
		seterrno(errsav);
	}
openbydev:
	if (nopen == 0) {
		int	target;
		int	lun;

		if (device != NULL && strncmp(device, "USCSI:", 6) == 0)
			device += 6;
		if (device == NULL || device[0] == '\0')
			return (0);

		f = open(device, O_RDONLY | O_NDELAY);
		if (f < 0)
			f = scgo_volopen(scgp, device);
		if (f < 0) {
			js_snprintf(scgp->errstr,
				    SCSI_ERRSTR_SIZE,
				"Cannot open '%s'", device);
			return (0);
		}

		if (busno < 0)
			busno = 0;	/* Use Fake number if not specified */

		if (scgo_ugettlun(f, &target, &lun) >= 0) {
			if (tgt >= 0 && tlun >= 0) {
				if (tgt != target || tlun != lun) {
					close(f);
					return (0);
				}
			}
			tgt = target;
			tlun = lun;
		} else {
			if (tgt < 0 || tlun < 0) {
				close(f);
				return (0);
			}
		}

		if (scglocal(scgp)->u.scg_files[busno][tgt][tlun] == -1)
			scglocal(scgp)->u.scg_files[busno][tgt][tlun] = f;
		scg_settarget(scgp, busno, tgt, tlun);

		return (++nopen);
	}
	return (nopen);
}

LOCAL int
scgo_volopen(scgp, devname)
	SCSI	*scgp;
	char	*devname;
{
	int	oerr = geterrno();
	int	f = -1;
	char	*name   = NULL;	/* Volume symbolic device name		*/
	char	*symdev = NULL;	/* /dev/... name based on "name" 	*/
	char	*mname  = NULL;	/* Volume media name based on "name"	*/

	if (!have_volmgt)
		return (-1);

#ifdef	VOLMGT_DEBUG
	scgp->debug++;
#endif
	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile,
			"scgo_volopen(%s)\n", devname);
	}

	/*
	 * We come here because trying to open "devname" did not work.
	 * First try to translate between a symbolic name and a /dev/...
	 * based device name. Then translate back to a symbolic name.
	 */
	symdev = volmgt_symdev(devname);
	if (symdev)
		name = volmgt_symname(symdev);
	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile,
			"volmgt_symdev(%s)=%s -> %s\n", devname, symdev, name);
	}

	/*
	 * If "devname" is not a symbolic device name, then it must be
	 * a /dev/... based device name. Try to translate it into a
	 * symbolic name. Then translate back to a /dev/... name.
	 */
	if (name == NULL) {
		name = volmgt_symname(devname);
		if (name)
			symdev = volmgt_symdev(name);
	}
	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile,
			"volmgt_symdev(%s)=%s -> %s\n", devname, symdev, name);
	}

	/*
	 * If we have been able to translate to a symbolic device name,
	 * translate this name into a volume management media name that
	 * may be used for opening.
	 */
	if (name)
		mname = media_findname(name);
	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile,
			"symdev %s name %s mname %s\n", symdev, name, mname);
	}

	/*
	 * Das scheint nur mit dem normierten /dev/rdsk/ *s2 Namen zu gehen.
	 */
	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile,
			"volmgt_inuse(%s) %d\n", symdev, volmgt_inuse(symdev));
	}
	if (mname)
		f = scgo_openmedia(scgp, mname);
	else if (name)
		f = -2;	/* Mark inaccessible drives with fd == -2 */

	/*
	 * Besonderen Fehlertext oder fprintf/errfile bei non-scanbus Open und
	 * wenn errrno == EBUSY && kein Mapping?
	 */
	if (name)
		free(name);
	if (symdev)
		free(symdev);
	if (mname)
		free(mname);
	seterrno(oerr);
#ifdef	VOLMGT_DEBUG
	scgp->debug--;
#endif
	return (f);
}

LOCAL int
scgo_openmedia(scgp, mname)
	SCSI	*scgp;
	char	*mname;
{
	int	f = -1;
	char	*device = NULL;
	struct	stat sb;

	if (mname == NULL)
		return (-1);

	/*
	 * Check whether the media name refers to a directory.
	 * In this case, the medium is partitioned and we need to
	 * check all partitions.
	 */
	if (stat(mname, &sb) >= 0) {
		if (S_ISDIR(sb.st_mode)) {
			char    name[128];
			int	i;

			/*
			 * First check partition '2', the whole disk.
			 */
			js_snprintf(name, sizeof (name), "%s/s2", mname);
			f = open(name, O_RDONLY | O_NDELAY);
			if (f >= 0)
				return (f);
			/*
			 * Now try all other partitions.
			 */
			for (i = 0; i < 16; i++) {
				if (i == 2)
					continue;
				js_snprintf(name, sizeof (name),
							"%s/s%d", mname, i);
				if (stat(name, &sb) >= 0)
					break;
			}
			if (i < 16) {
				device = mname;
			}
		} else {
			device = mname;
		}
	}
	if (device)
		f = open(device, O_RDONLY | O_NDELAY);
	return (f);
}

LOCAL int
scgo_uclose(scgp)
	SCSI	*scgp;
{
	register int	f;
	register int	b;
	register int	t;
	register int	l;

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

	for (b = 0; b < MAX_SCG; b++) {
		for (t = 0; t < MAX_TGT; t++) {
			for (l = 0; l < MAX_LUN; l++) {
				f = scglocal(scgp)->u.scg_files[b][t][l];
				if (f >= 0)
					close(f);
				scglocal(scgp)->u.scg_files[b][t][l] = (short)-1;
			}
		}
	}
	return (0);
}

LOCAL int
scgo_ucinfo(f, cp, debug)
	int		f;
	struct dk_cinfo *cp;
	int		debug;
{
	fillbytes(cp, sizeof (*cp), '\0');

	if (ioctl(f, DKIOCINFO, cp) < 0)
		return (-1);

	if (debug <= 0)
		return (0);

	js_fprintf(stderr, "cname:		'%s'\n", cp->dki_cname);
	js_fprintf(stderr, "ctype:		0x%04hX %hd\n", cp->dki_ctype, cp->dki_ctype);
	js_fprintf(stderr, "cflags:		0x%04hX\n", cp->dki_flags);
	js_fprintf(stderr, "cnum:		%hd\n", cp->dki_cnum);
#ifdef	__EVER__
	js_fprintf(stderr, "addr:		%d\n", cp->dki_addr);
	js_fprintf(stderr, "space:		%d\n", cp->dki_space);
	js_fprintf(stderr, "prio:		%d\n", cp->dki_prio);
	js_fprintf(stderr, "vec:		%d\n", cp->dki_vec);
#endif
	js_fprintf(stderr, "dname:		'%s'\n", cp->dki_dname);
	js_fprintf(stderr, "unit:		%d\n", cp->dki_unit);
	js_fprintf(stderr, "slave:		%d %04o Tgt: %d Lun: %d\n",
				cp->dki_slave, cp->dki_slave,
				TARGET(cp->dki_slave), LUN(cp->dki_slave));
	js_fprintf(stderr, "partition:	%hd\n", cp->dki_partition);
	js_fprintf(stderr, "maxtransfer:	%d (%d)\n",
				cp->dki_maxtransfer,
				cp->dki_maxtransfer * DEV_BSIZE);
	return (0);
}

LOCAL int
scgo_ugettlun(f, tgtp, lunp)
	int	f;
	int	*tgtp;
	int	*lunp;
{
	struct dk_cinfo ci;

	if (scgo_ucinfo(f, &ci, 0) < 0)
		return (-1);
	if (tgtp)
		*tgtp = TARGET(ci.dki_slave);
	if (lunp)
		*lunp = LUN(ci.dki_slave);
	return (0);
}

LOCAL long
scgo_umaxdma(scgp, amt)
	SCSI	*scgp;
	long	amt;
{
	register int	b;
	register int	t;
	register int	l;
	long		maxdma = -1L;
	int		f;
	struct dk_cinfo ci;
	BOOL		found_ide = FALSE;

	if (scgp->local == NULL)
		return (-1L);

	for (b = 0; b < MAX_SCG; b++) {
		for (t = 0; t < MAX_TGT; t++) {
			for (l = 0; l < MAX_LUN; l++) {
				if ((f = scglocal(scgp)->u.scg_files[b][t][l]) < 0)
					continue;
				if (scgo_ucinfo(f, &ci, scgp->debug) < 0)
					continue;
				if (maxdma < 0)
					maxdma = (long)(ci.dki_maxtransfer * DEV_BSIZE);
				if (maxdma > (long)(ci.dki_maxtransfer * DEV_BSIZE))
					maxdma = (long)(ci.dki_maxtransfer * DEV_BSIZE);
				if (streql(ci.dki_cname, "ide"))
					found_ide = TRUE;
			}
		}
	}

#if	defined(__i386_) || defined(i386)
	/*
	 * At least on Solaris 9 x86, DKIOCINFO returns a wrong value
	 * for dki_maxtransfer if the target is an ATAPI drive.
	 * Without DMA, it seems to work if we use 256 kB DMA size for ATAPI,
	 * but if we allow DMA, only 68 kB will work (for more we get a silent
	 * DMA residual count == DMA transfer count).
	 * For this reason, we try to figure out the correct value for 'ide'
	 * by retrieving the (correct) value from a ide hard disk.
	 */
	if (found_ide) {
		if ((f = scgo_openide()) >= 0) {
#ifdef	sould_we
			long omaxdma = maxdma;
#endif

			if (scgo_ucinfo(f, &ci, scgp->debug) >= 0) {
				if (maxdma < 0)
					maxdma = (long)(ci.dki_maxtransfer * DEV_BSIZE);
				if (maxdma > (long)(ci.dki_maxtransfer * DEV_BSIZE))
					maxdma = (long)(ci.dki_maxtransfer * DEV_BSIZE);
			}
			close(f);
#ifdef	sould_we
			/*
			 * The kernel returns 56 kB but we tested that 68 kB works.
			 */
			if (omaxdma > maxdma && maxdma == (112 * DEV_BSIZE))
				maxdma = 136 * DEV_BSIZE;
#endif
		} else {
			/*
			 * No IDE disk on this system?
			 */
			if (maxdma == (512 * DEV_BSIZE))
				maxdma = MAX_DMA_SUN386;
		}
	}
#endif
	/*
	 * The Sun tape driver does not support to retrieve the max DMA count.
	 * Use the knwoledge about default DMA sizes in this case.
	 */
	if (maxdma < 0)
		maxdma = scgo_maxdma(scgp, amt);

	return (maxdma);
}

#if	defined(__i386_) || defined(i386)
LOCAL int
scgo_openide()
{
	char	buf[20];
	int	b;
	int	t;
	int	f = -1;

	for (b = 0; b < 5; b++) {
		for (t = 0; t < 2; t++) {
			js_snprintf(buf, sizeof (buf),
				"/dev/rdsk/c%dd%dp0", b, t);
			if ((f = open(buf, O_RDONLY | O_NDELAY)) >= 0)
				goto out;
		}
	}
out:
	return (f);
}
#endif

LOCAL int
scgo_unumbus(scgp)
	SCSI	*scgp;
{
	return (MAX_SCG);
}

LOCAL BOOL
scgo_uhavebus(scgp, busno)
	SCSI	*scgp;
	int	busno;
{
	register int	t;
	register int	l;

	if (scgp->local == NULL || busno < 0 || busno >= MAX_SCG)
		return (FALSE);

	for (t = 0; t < MAX_TGT; t++) {
		for (l = 0; l < MAX_LUN; l++)
			if (scglocal(scgp)->u.scg_files[busno][t][l] >= 0)
				return (TRUE);
	}
	return (FALSE);
}

LOCAL int
scgo_ufileno(scgp, busno, tgt, tlun)
	SCSI	*scgp;
	int	busno;
	int	tgt;
	int	tlun;
{
	if (scgp->local == NULL ||
	    busno < 0 || busno >= MAX_SCG ||
	    tgt < 0 || tgt >= MAX_TGT ||
	    tlun < 0 || tlun >= MAX_LUN)
		return (-1);

	return ((int)scglocal(scgp)->u.scg_files[busno][tgt][tlun]);
}

LOCAL int
scgo_uinitiator_id(scgp)
	SCSI	*scgp;
{
	return (-1);
}

LOCAL int
scgo_uisatapi(scgp)
	SCSI	*scgp;
{
	char		devname[32];
	char		symlinkname[MAXPATHLEN];
	int		len;
	struct dk_cinfo ci;

	if (ioctl(scgp->fd, DKIOCINFO, &ci) < 0)
		return (-1);

	js_snprintf(devname, sizeof (devname), "/dev/rdsk/c%dt%dd%ds2",
		scg_scsibus(scgp), scg_target(scgp), scg_lun(scgp));

	symlinkname[0] = '\0';
	len = readlink(devname, symlinkname, sizeof (symlinkname));
	if (len > 0)
		symlinkname[len] = '\0';

	if (len >= 0 && strstr(symlinkname, "ide") != NULL)
		return (TRUE);
	else
		return (FALSE);
}

LOCAL int
scgo_ureset(scgp, what)
	SCSI	*scgp;
	int	what;
{
	struct uscsi_cmd req;

	if (what == SCG_RESET_NOP)
		return (0);

	fillbytes(&req, sizeof (req), '\0');

	if (what == SCG_RESET_TGT) {
		req.uscsi_flags = USCSI_RESET | USCSI_SILENT;	/* reset target */
	} else if (what != SCG_RESET_BUS) {
		req.uscsi_flags = USCSI_RESET_ALL | USCSI_SILENT; /* reset bus */
	} else {
		errno = EINVAL;
		return (-1);
	}

	return (ioctl(scgp->fd, USCSICMD, &req));
}

LOCAL int
scgo_usend(scgp)
	SCSI		*scgp;
{
	struct scg_cmd	*sp = scgp->scmd;
	struct uscsi_cmd req;
	int		ret;
static	uid_t		cureuid = 0;	/* XXX Hack until we have uid management */

	if (scgp->fd < 0) {
		sp->error = SCG_FATAL;
		return (0);
	}

	fillbytes(&req, sizeof (req), '\0');

	req.uscsi_flags = USCSI_SILENT | USCSI_DIAGNOSE | USCSI_RQENABLE;

	if (sp->flags & SCG_RECV_DATA) {
		req.uscsi_flags |= USCSI_READ;
	} else if (sp->size > 0) {
		req.uscsi_flags |= USCSI_WRITE;
	}
	req.uscsi_buflen	= sp->size;
	req.uscsi_bufaddr	= sp->addr;
	req.uscsi_timeout	= sp->timeout;
	req.uscsi_cdblen	= sp->cdb_len;
	req.uscsi_rqbuf		= (caddr_t) sp->u_sense.cmd_sense;
	req.uscsi_rqlen		= sp->sense_len;
	req.uscsi_cdb		= (caddr_t) &sp->cdb;

	if (cureuid != 0)
		seteuid(0);
again:
	errno = 0;
	ret = ioctl(scgp->fd, USCSICMD, &req);

	if (ret < 0 && geterrno() == EPERM) {	/* XXX Hack until we have uid management */
		cureuid = geteuid();
		if (seteuid(0) >= 0)
			goto again;
	}
	if (cureuid != 0)
		seteuid(cureuid);

	if (scgp->debug > 0) {
		js_fprintf((FILE *)scgp->errfile, "ret: %d errno: %d (%s)\n", ret, errno, errmsgstr(errno));
		js_fprintf((FILE *)scgp->errfile, "uscsi_flags:     0x%x\n", req.uscsi_flags);
		js_fprintf((FILE *)scgp->errfile, "uscsi_status:    0x%x\n", req.uscsi_status);
		js_fprintf((FILE *)scgp->errfile, "uscsi_timeout:   %d\n", req.uscsi_timeout);
		js_fprintf((FILE *)scgp->errfile, "uscsi_bufaddr:   0x%lx\n", (long)req.uscsi_bufaddr);
								/*
								 * Cast auf int OK solange sp->size
								 * auch ein int bleibt.
								 */
		js_fprintf((FILE *)scgp->errfile, "uscsi_buflen:    %d\n", (int)req.uscsi_buflen);
		js_fprintf((FILE *)scgp->errfile, "uscsi_resid:     %d\n", (int)req.uscsi_resid);
		js_fprintf((FILE *)scgp->errfile, "uscsi_rqlen:     %d\n", req.uscsi_rqlen);
		js_fprintf((FILE *)scgp->errfile, "uscsi_rqstatus:  0x%x\n", req.uscsi_rqstatus);
		js_fprintf((FILE *)scgp->errfile, "uscsi_rqresid:   %d\n", req.uscsi_rqresid);
		js_fprintf((FILE *)scgp->errfile, "uscsi_rqbuf ptr: 0x%lx\n", (long)req.uscsi_rqbuf);
		js_fprintf((FILE *)scgp->errfile, "uscsi_rqbuf:     ");
		if (req.uscsi_rqbuf != NULL && req.uscsi_rqlen > req.uscsi_rqresid) {
			int	i;
			int	len = req.uscsi_rqlen - req.uscsi_rqresid;

			for (i = 0; i < len; i++) {
				js_fprintf((FILE *)scgp->errfile, "0x%02X ", ((char *)req.uscsi_rqbuf)[i]);
			}
			js_fprintf((FILE *)scgp->errfile, "\n");
		} else {
			js_fprintf((FILE *)scgp->errfile, "<data not available>\n");
		}
	}
	if (ret < 0) {
		sp->ux_errno = geterrno();
		/*
		 * Check if SCSI command cound not be send at all.
		 */
		if (sp->ux_errno == ENOTTY && scgo_uisatapi(scgp) == TRUE) {
			if (scgp->debug > 0) {
				js_fprintf((FILE *)scgp->errfile,
					"ENOTTY atapi: %d\n", scgo_uisatapi(scgp));
			}
			sp->error = SCG_FATAL;
			return (0);
		}
		if (errno == ENXIO) {
			sp->error = SCG_FATAL;
			return (0);
		}
		if (errno == ENOTTY || errno == EINVAL || errno == EACCES || errno == EPERM) {
			return (-1);
		}
	} else {
		sp->ux_errno = 0;
	}
	ret			= 0;
	sp->sense_count		= req.uscsi_rqlen - req.uscsi_rqresid;
	sp->resid		= req.uscsi_resid;
	sp->u_scb.cmd_scb[0]	= req.uscsi_status;

	if ((req.uscsi_status & STATUS_MASK) == STATUS_GOOD) {
		sp->error = SCG_NO_ERROR;
		return (0);
	}
	if (req.uscsi_rqstatus == 0 &&
	    ((req.uscsi_status & STATUS_MASK) == STATUS_CHECK)) {
		sp->error = SCG_NO_ERROR;
		return (0);
	}
	if (req.uscsi_status & (STATUS_TERMINATED |
	    STATUS_RESERVATION_CONFLICT)) {
		sp->error = SCG_FATAL;
	}
	if (req.uscsi_status != 0) {
		/*
		 * This is most likely wrong. There seems to be no way
		 * to produce SCG_RETRYABLE with USCSI.
		 */
		sp->error = SCG_RETRYABLE;
	}

	return (ret);
}
#endif	/* USE_USCSI */
