www.pudn.com > cdrecord.zip > scsi-linux-sg.c


/* @(#)scsi-linux-sg.c	1.26 98/10/06 Copyright 1997 J. Schilling */
#ifndef lint
static	char __sccsid[] =
	"@(#)scsi-linux-sg.c	1.26 98/10/06 Copyright 1997 J. Schilling";
#endif
/*
 *	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 scg 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
 *
 *	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, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#if HAVE_CONFIG_H
#include 
#endif

#include 
#include 

#ifdef HAVE_LINUX_PG_H
#       define   USE_PG
#endif

#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

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

LOCAL	int	scgfile = -1;	/* Used for SG_GET_BUFSIZE ioctl()	*/
LOCAL	short	scgfiles[MAX_SCG][MAX_TGT][MAX_LUN];
LOCAL	short	buscookies[MAX_SCG];

#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	*/

LOCAL	int	pack_id = 5;		/* Should be a random number	*/
LOCAL	char	*SCSIbuf = (char *)-1;

#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	MISALIGN
LOCAL	int	scsi_getint	__PR((int *ip));
#endif
LOCAL	int	scsi_send	__PR((int f, struct scg_cmd *sp));
LOCAL	BOOL	sg_setup	__PR((int f, int busno, int tgt, int tlun));
LOCAL	void	sg_initdev	__PR((int f));
LOCAL	int	sg_mapbus	__PR((int ino));
LOCAL	BOOL	sg_mapdev	__PR((int f, int *busp, int *tgtp, int *lunp,
							int *chanp, int *inop));
LOCAL	void	sg_settimeout	__PR((int f, int timeout));

EXPORT
int scsi_open(device, busno, tgt, tlun)
	char	*device;
	int	busno;
	int	tgt;
	int	tlun;
{
	register int	f;
	register int	i;
	register int	b;
	register int	t;
	register int	l;
	register int	nopen = 0;
	char		devname[64];

	for (b=0; b < MAX_SCG; b++) {
		buscookies[b] = (short)-1;
		for (t=0; t < MAX_TGT; t++) {
			for (l=0; l < MAX_LUN ; l++)
				scgfiles[b][t][l] = (short)-1;
		}
	}
	if (*device != '\0' || (busno == -2 && tgt == -2))
		goto openbydev;

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

	for (i=0; i < 32; i++) {
		sprintf(devname, "/dev/sg%d", i);
		f = open(devname, 2);
		if (f < 0) {
			if (errno != ENOENT && errno != ENXIO && errno != ENODEV)
				comerr("Cannot open '%s'.\n", devname);
		} else {
			if (sg_setup(f, busno, tgt, tlun))
				return (++nopen);
			if (busno < 0 && tgt < 0 && tlun < 0)
				nopen++;
		}
	}
	if (nopen == 0) for (i=0; i <= 25; i++) {
		sprintf(devname, "/dev/sg%c", i+'a');
		f = open(devname, 2);
		if (f < 0) {
			if (errno != ENOENT && errno != ENXIO && errno != ENODEV)
				comerr("Cannot open '%s'.\n", devname);
		} else {
			if (sg_setup(f, busno, tgt, tlun))
				return (++nopen);
			if (busno < 0 && tgt < 0 && tlun < 0)
				nopen++;
		}
	}
openbydev:
	if (*device != '\0') {
		f = open(device, 2);
		if (f < 0 && errno == ENOENT)
			goto openpg;

		if (!sg_mapdev(f, &busno, &tgt, &tlun, 0, 0)) {
			close(f);
			goto openpg;
		}

		if (scsibus < 0)
			scsibus = busno;
		if (target < 0)
			target = tgt;
		if (lun < 0)
			lun = tlun;

		if (sg_setup(f, busno, tgt, tlun))
			return (++nopen);
	}
openpg:
#ifdef	USE_PG
	nopen += pg_open(device, busno, tgt, tlun);
#endif
	return (nopen);
}

LOCAL BOOL
sg_setup(f, busno, tgt, tlun)
	int	f;
	int	busno;
	int	tgt;
	int	tlun;
{
	int	n;
	int	Chan;
	int	Ino;
	int	Bus;
	int	Target;
	int	Lun;
	BOOL	onetarget = FALSE;


	if (scsibus >= 0 && target >= 0 && lun >= 0)
		onetarget = TRUE;

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

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

	if (scgfiles[Bus][Target][Lun] == (short)-1)
		scgfiles[Bus][Target][Lun] = (short)f;

	if (onetarget) {
		if (Bus == busno && Target == tgt && Lun == tlun) {
			sg_initdev(f);
			scgfile = f;	/* remember file for ioctl's */
			return (TRUE);
		} else {
			scgfiles[Bus][Target][Lun] = (short)-1;
			close(f);
		}
	} else {
		sg_initdev(f);
		if (scgfile < 0)
			scgfile = f;	/* remember file for ioctl's */
	}
	return (FALSE);
}

LOCAL void
sg_initdev(f)
	int	f;
{
	struct sg_rep {
		struct sg_header	hd;
		unsigned char		rbuf[100];
	} sg_rep;
	int	n;

	/* 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);

	while (read(f, &sg_rep, sizeof(sg_rep)) >= 0 || errno != EAGAIN)
		;

	fcntl(f, F_SETFL, n);

	sg_settimeout(f, deftimeout);
}

LOCAL int
sg_mapbus(ino)
	int	ino;
{
	register int	i;

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

		if (buscookies[i] == ino)
			return (i);
	}
	return (0);
}

LOCAL BOOL
sg_mapdev(f, busp, tgtp, lunp, chanp, inop)
	int	f;
	int	*busp;
	int	*tgtp;
	int	*lunp;
	int	*chanp;
	int	*inop;
{
	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 (ioctl(f, SCSI_IOCTL_GET_IDLUN, &sg_id))
		return (FALSE);
	if (debug)
		printf("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 (debug) {
		printf("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);
}

LOCAL long
scsi_maxdma()
{
	long maxdma = MAX_DMA_LINUX;

#ifdef	SG_GET_BUFSIZE
	/*
	 * We assume that all /dev/sg instances use the same
	 * maximum buffer size.
	 */
	if ((maxdma = ioctl(scgfile, SG_GET_BUFSIZE, 0)) < 0) {
		if (scgfile >= 0) {
			maxdma = MAX_DMA_LINUX;
		}
	}
#endif
#ifdef	USE_PG
	if (scsibus == pgbus)
		return (pg_maxdma());
	if ((scsibus < 0) && (pg_maxdma() < maxdma))
		return (pg_maxdma());
#endif
	return (maxdma);
}

EXPORT void *
scsi_getbuf(amt)
	long	amt;
{
	char	*ret;

	if (scg_maxdma == 0)
		scg_maxdma = scsi_maxdma();

	if (amt <= 0 || amt > scg_maxdma)
		return ((void *)0);
	if (debug)
		printf("scsi_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);
	ret += getpagesize();
	SCSIbuf = ret;
	return ((void *)ret);
}

EXPORT
BOOL scsi_havebus(busno)
	int	busno;
{
	register int	t;
	register int	l;

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

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

EXPORT
int scsi_fileno(busno, tgt, tlun)
	int	busno;
	int	tgt;
	int	tlun;
{
	if (busno < 0 || busno >= MAX_SCG ||
	    tgt < 0 || tgt >= MAX_TGT ||
	    tlun < 0 || tlun >= MAX_LUN)
		return (-1);

	return ((int)scgfiles[busno][tgt][tlun]);
}

EXPORT
int scsi_isatapi()
{
#ifdef	USE_PG
	if (scsibus == pgbus)
		return (pg_isatapi());
#endif

#ifdef	SG_EMULATED_HOST
	{
	int	emulated = FALSE;
	int	f = scsi_fileno(scsibus, target, lun);

	if (ioctl(f, SG_EMULATED_HOST, &emulated) >= 0)
		return (emulated != 0);
	}
#endif
	return (-1);
}

EXPORT
int scsireset()
{
#ifdef	USE_PG
	if (scsibus == pgbus)
		return (pg_reset());
#endif
	/*
	 * Do we have a SCSI reset in the Linux sg driver?
	 */
	return (-1);
}

LOCAL void
sg_settimeout(f, tmo)
	int	f;
	int	tmo;
{
	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
LOCAL int
scsi_getint(ip)
	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)	scsi_getint(&(a))
#else
#define	GETINT(a)	(a)
#endif

LOCAL int
scsi_send(int f, struct scg_cmd *sp)
{
	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;
		return (0);
	}
#ifdef	USE_PG
	if (scsibus == pgbus)
		return (pg_send(f, sp));
#endif
	if (sp->timeout != deftimeout)
		sg_settimeout(f, sp->timeout);


	sgp2 = sgp = &sg_rq;
	if (sp->addr == SCSIbuf) {
		sgp = (struct sg_rq *)
			(SCSIbuf - (sizeof(struct sg_header) + amt));
		sgp2 = (struct sg_rq *)
			(SCSIbuf - (sizeof(struct sg_header)));
	} else {
		if (debug) {
			printf("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)) {
			errno = ENOMEM;
			return (-1);
		}
	}

	/*
	 * 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 = 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 = 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, deftimeout);
		return (-1);
	} else if (amt != i) {
		errmsg("scsi_send(%s) wrote %d bytes (expected %d).\n",
						scsi_command, amt, i);
	}

	if (sp->addr == 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, 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 (sgp->hd.sense_buffer[0] != 0) {
					/*
					 * The Linux SCSI system up to 2.1.xx
					 * trashes the status byte in the
					 * kernel. Until this gets fixed, we
					 * need this hack.
					 */
					sp->error = SCG_RETRYABLE;
					status_byte = 2;
					sgp->hd.sense_len = SG_MAX_SENSE;
				}
				break;

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

		default:
				sp->error = SCG_RETRYABLE;
				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 & 2) {
			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;
			scsitimes();

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

				sp->ux_errno = 0;
				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->error = SCG_RETRYABLE;
			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 (verbose > 0 && debug) {
#ifdef	SG_GET_BUFSIZE
		printf("status: %X 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
		printf("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
		printf("sense: ");
		for (i=0; i < 16; i++)
			printf("%02X ", sgp->hd.sense_buffer[i]);
		printf("\n");
#endif
	}

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