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; }