/* @(#)scsi-next.c	1.30 01/03/18 Copyright 1997 J. Schilling */
#ifndef lint
static	char __sccsid[] =
	"@(#)scsi-next.c	1.30 01/03/18 Copyright 1997 J. Schilling";
#endif
/*
 *	Interface for the NeXT Step generic SCSI implementation.
 *
 *	This is a hack, that tries to emulate the functionality
 *	of the scg driver.
 *
 *	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) 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 as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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 <bsd/dev/scsireg.h>

/*
 *	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.
 */
LOCAL	char	_scg_trans_version[] = "scsi-next.c-1.30";	/* The version for this transport*/

#define	MAX_SCG		16	/* Max # of SCSI controllers */
#define	MAX_TGT		16
#define	MAX_LUN		8

struct scg_local {
	short	scgfiles[MAX_SCG][MAX_TGT][MAX_LUN];
	int	scgfile;
	int	max_scsibus;
	int	cur_scsibus;
	int	cur_target;
	int	cur_lun;
};
#define scglocal(p)	((struct scg_local *)((p)->local)) 

/*#define	MAX_DMA_NEXT	(32*1024)*/
#define	MAX_DMA_NEXT	(64*1024)	/* Check if this is not too big */


LOCAL	BOOL	scg_setup	__PR((SCSI *scgp, int busno, int tgt, int tlun,
								BOOL ex));

/*
 * 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:
			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_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;
	char		devname[64];

	if (busno >= MAX_SCG || tgt >= MAX_TGT || tlun >= MAX_LUN) {
		errno = EINVAL;
		if (scgp->errstr)
			snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
				"Illegal value for busno, target or lun '%d,%d,%d'",
				busno, tgt, tlun);
		return (-1);
	}

	if ((device != NULL && *device != '\0') || (busno == -2 && tgt == -2)) {
		errno = EINVAL;
		if (scgp->errstr)
			snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
				"Open by 'devname' not supported on this OS");
		return (-1);
	}

	if (scgp->local == NULL) {
		scgp->local = malloc(sizeof(struct scg_local));
		if (scgp->local == NULL)
			return (0);

		scglocal(scgp)->scgfile		= -1;
		scglocal(scgp)->max_scsibus	= -1;
		scglocal(scgp)->cur_scsibus	= -1;
		scglocal(scgp)->cur_target	= -1;
		scglocal(scgp)->cur_lun		= -1;
	}

	for (i=0; i < 4; i++) {
		snprintf(devname, sizeof(devname), "/dev/sg%d", i);
		f = open(devname, O_RDWR);
		if (scgp->debug > 0)
			errmsg("open(devname: '%s') : %d\n", devname, f);
		if (f < 0)
			continue;
		scglocal(scgp)->scgfile = f;
		break;

	}
	if (f >= 0) {
		if (scglocal(scgp)->max_scsibus < 0) {
			for (i=0; i < MAX_SCG; i++) {
				if (!SCGO_HAVEBUS(scgp, i))
					break;
			}
			scglocal(scgp)->max_scsibus = i;
		}
		if (scgp->debug > 0) {
			fprintf((FILE *)scgp->errfile,
				"maxbus: %d\n", scglocal(scgp)->max_scsibus);
		}
		if (scglocal(scgp)->max_scsibus <= 0) {
			scglocal(scgp)->max_scsibus = 1;
			scglocal(scgp)->cur_scsibus = 0;
		}

       	        ioctl(f, SGIOCENAS);
		if (busno > 0 && tgt > 0 && tlun > 0)
			scg_setup(scgp, busno, tgt, tlun, TRUE);
		return(1);
	}
	if (scgp->errstr)
		snprintf(scgp->errstr, SCSI_ERRSTR_SIZE,
			"Cannot open '/dev/sg*'");
	return (0);
}

LOCAL int
scgo_close(scgp)
	SCSI	*scgp;
{
	if (scgp->local == NULL)
		return (-1);

	if (scglocal(scgp)->scgfile >= 0)
		close(scglocal(scgp)->scgfile);
	scglocal(scgp)->scgfile = -1;
	return (0);
}

LOCAL BOOL
scg_setup(scgp, busno, tgt, tlun, ex)
	SCSI	*scgp;
	int	busno;
	int	tgt;
	int	tlun;
	BOOL	ex;
{
	scsi_adr_t sadr;

	sadr.sa_target = tgt;
	sadr.sa_lun = tlun;

	if (scgp->debug > 0) {
		fprintf((FILE *)scgp->errfile,
			"scg_setup curbus %d -> %d\n", scglocal(scgp)->cur_scsibus, busno);
	}

	if (scgp->debug > 0 && ((scglocal(scgp)->cur_scsibus < 0 || scglocal(scgp)->cur_scsibus != busno)))
		fprintf((FILE *)scgp->errfile, "setting SCSI bus to: %d\n", busno); 
	if ((scglocal(scgp)->cur_scsibus < 0 || scglocal(scgp)->cur_scsibus != busno) &&
				ioctl(scglocal(scgp)->scgfile, SGIOCCNTR, &busno) < 0) {

		scglocal(scgp)->cur_scsibus = -1;	/* Driver is in undefined state */
		if (ex)
/*			comerr("Cannot set SCSI bus\n");*/
			errmsg("Cannot set SCSI bus\n");
		return (FALSE);
	}
	scglocal(scgp)->cur_scsibus	= busno;

	if (scgp->debug > 0) {
		fprintf((FILE *)scgp->errfile,
			"setting target/lun to: %d/%d\n", tgt, tlun);
	}
	if (ioctl(scglocal(scgp)->scgfile, SGIOCSTL, &sadr) < 0) {
		if (ex)
			comerr("Cannot set SCSI address\n");
		return (FALSE);
	}
	scglocal(scgp)->cur_scsibus	= busno;
	scglocal(scgp)->cur_target	= tgt;
	scglocal(scgp)->cur_lun		= tlun;
	return (TRUE);
}

LOCAL long
scgo_maxdma(scgp, amt)
	SCSI	*scgp;
	long	amt;
{
	long maxdma = MAX_DMA_NEXT;
#ifdef	SGIOCMAXDMA
	int  m;

	if (ioctl(scglocal(scgp)->scgfile, SGIOCMAXDMA, &m) >= 0) {
		maxdma = m;
		if (scgp->debug > 0) {
			fprintf((FILE *)scgp->errfile,
				"maxdma: %d\n", maxdma);
		}
	}
#endif
	return (maxdma);
}
#ifdef	XXX
#define SGIOCENAS	_IO(  's', 2)		 	/* enable autosense */
#define SGIOCDAS	_IO(  's', 3)			/* disable autosense */
#define SGIOCRST	_IO(  's', 4)			/* reset SCSI bus */
#define SGIOCCNTR       _IOW( 's', 6, int)              /* select controller */
#define SGIOCGAS	_IOR( 's', 7, int)		/* get autosense */
#define SGIOCMAXDMA	_IOR( 's', 8, int)		/* max DMA size */
#define SGIOCNUMTARGS	_IOR( 's', 9, int)		/* # of targets/bus */
#endif

LOCAL void *
scgo_getbuf(scgp, amt)
	SCSI	*scgp;
	long	amt;
{
	if (scgp->debug > 0) {
		fprintf((FILE *)scgp->errfile,
			"scgo_getbuf: %ld bytes\n", amt);
	}
	scgp->bufbase = valloc((size_t)(amt));
	return (scgp->bufbase);
}

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

LOCAL BOOL
scgo_havebus(scgp, busno)
	SCSI	*scgp;
	int	busno;
{
	if (busno < 0 || busno >= MAX_SCG)
		return (FALSE);

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

	if (scglocal(scgp)->max_scsibus > 0 && busno >= scglocal(scgp)->max_scsibus)
		return (FALSE);

	return (scg_setup(scgp, busno, 0, 0, FALSE));
}

LOCAL int
scgo_fileno(scgp, busno, tgt, tlun)
	SCSI	*scgp;
	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 (scglocal(scgp)->max_scsibus > 0 && busno >= scglocal(scgp)->max_scsibus)
		return (-1);

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

	if ((busno != scglocal(scgp)->cur_scsibus) || (tgt != scglocal(scgp)->cur_target) || (tlun != scglocal(scgp)->cur_lun)) {
		if (!scg_setup(scgp, busno, tgt, tlun, FALSE))
			return (-1);
	}
	return (scglocal(scgp)->scgfile);
}

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

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, SGIOCRST, 0));
}

LOCAL int
scgo_send(scgp)
	SCSI		*scgp;
{
	struct scg_cmd	*sp = scgp->scmd;
	struct scsi_req	req;
	register long	*lp1;
	register long	*lp2;
	int		ret = 0;

	if (scgp->fd < 0 || (sp->cdb_len > sizeof(req.sr_cdb))) {
		sp->error = SCG_FATAL;
		sp->ux_errno = EIO;
		return (0);
	}
	memset(&req, 0, sizeof(req));
	memcpy(&req.sr_cdb, sp->cdb.cmd_cdb, sp->cdb_len);
	if (sp->size) {
		req.sr_dma_dir = SR_DMA_WR;
		if (sp->flags & SCG_RECV_DATA)
			req.sr_dma_dir = SR_DMA_RD;
	}
	req.sr_addr = sp->addr;
	req.sr_dma_max = sp->size;
	req.sr_ioto = sp->timeout;
	if (ioctl(scgp->fd, SGIOCREQ, (void *)&req) < 0) {
		ret  = -1;
		sp->ux_errno = errno;
		if (sp->ux_errno != ENOTTY)
			ret = 0;
	} else {
		sp->ux_errno = 0;
	}
	if (scgp->debug > 0) {
		fprintf((FILE *)scgp->errfile, "dma_dir:     %X\n", req.sr_dma_dir);
		fprintf((FILE *)scgp->errfile, "dma_addr:    %X\n", req.sr_addr);
		fprintf((FILE *)scgp->errfile, "io_time:     %d\n", req.sr_ioto);
		fprintf((FILE *)scgp->errfile, "io_status:   %d\n", req.sr_io_status);
		fprintf((FILE *)scgp->errfile, "scsi_status: %X\n", req.sr_scsi_status);
		fprintf((FILE *)scgp->errfile, "dma_xfer:    %d\n", req.sr_dma_xfr);
	}
	sp->u_scb.cmd_scb[0] = req.sr_scsi_status;
	sp->sense_count = sizeof(esense_reply_t);
	if (sp->sense_count > sp->sense_len)
		sp->sense_count = sp->sense_len;
	if (sp->sense_count > SCG_MAX_SENSE)
		sp->sense_count = SCG_MAX_SENSE;
	if (sp->sense_count < 0)
		sp->sense_count = 0;
	memcpy(sp->u_sense.cmd_sense, &req.sr_esense, sp->sense_count);
	sp->resid = sp->size - req.sr_dma_xfr;

	switch (req.sr_io_status) {

	case SR_IOST_GOOD:	sp->error = SCG_NO_ERROR;	break;

	case SR_IOST_CHKSNV:	sp->sense_count = 0;
	case SR_IOST_CHKSV:	sp->error = SCG_RETRYABLE;
				break;
	
	case SR_IOST_SELTO:
	case SR_IOST_DMAOR:
				sp->error = SCG_FATAL;		break;

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

	case SR_IOST_PERM:
	case SR_IOST_NOPEN:
				sp->error = SCG_FATAL;
				ret = (-1);
				break;

	default:		sp->error = SCG_RETRYABLE;	break;

	}
	return (ret);
}
