diff options
Diffstat (limited to 'libusal/scsi-linux-sg.c')
-rw-r--r-- | libusal/scsi-linux-sg.c | 1754 |
1 files changed, 1754 insertions, 0 deletions
diff --git a/libusal/scsi-linux-sg.c b/libusal/scsi-linux-sg.c new file mode 100644 index 0000000..81d33d4 --- /dev/null +++ b/libusal/scsi-linux-sg.c @@ -0,0 +1,1754 @@ +/* + * 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> +#include <sys/utsname.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 + +#include <glob.h> + +/* + * 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 1256 /* 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]; + char *filenames[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 + */ + +#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*/ + + +#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, char *origname); +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, BOOL beQuiet); + +static BOOL get_max_secs(char *dirpath, int *outval); + +#if defined(USE_PG) && !defined(USE_PG_ONLY) +#include "scsi-linux-pg.c" +#endif +#ifdef USE_OLD_ATAPI +#include "scsi-linux-ata.c" +#endif + +BOOL check_linux_26() { + int gen, tmp; + struct utsname buf; + return ( 0==uname( &buf ) && sscanf(buf.release, "%d.%d", &gen, &tmp)>1 && tmp>=6); +} + +int sg_open_excl(char *device, int mode, BOOL beQuiet) +{ + int f; + int i=0; + long interval = beQuiet ? 400000 : 1000000; + + 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++) { + if(!beQuiet) + fprintf(stderr, "Error trying to open %s exclusively (%s)... %s\n", + device, strerror(errno), + (i<4)?"retrying in 1 second.":"giving up."); + usleep(interval + interval * rand()/(RAND_MAX+1.0)); + f = open(device, mode|O_EXCL); + } + if(i==5 && !beQuiet) { + FILE *g = fopen("/proc/mounts", "r"); + if(g) { + char buf[80]; + unsigned int len=strlen(device); + while(!feof(g) && !ferror(g)) { + if(fgets(buf, 79, g) && 0==strncmp(device, buf, len)) { + fprintf(stderr, "WARNING: %s seems to be mounted!\n", device); + } + } + fclose(g); + } + } + return f; +} + +#if 0 +// Dead code, that sysfs parts may become deprecated soon +void map_sg_to_block(char *device, int len) { + char globpat[100]; + glob_t globbuf; + snprintf(globpat, 100, "/sys/class/scsi_generic/%s/device/block:*", device+5); + memset(&globbuf, 0, sizeof(glob_t)); + if(0==glob(globpat, GLOB_DOOFFS | GLOB_NOSORT, NULL, &globbuf)) { + char *p = strrchr(globbuf.gl_pathv[0], ':'); + if(p) snprintf(device, len, "/dev/%s", p+1); + } + globfree(&globbuf); +} +#endif + +/* + * 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 +#error pg-junk + /* + * 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); +} + +#define in_scanmode (busno < 0 && tgt < 0 && tlun < 0) + +/* + * b/t/l is chopped of the device string. + */ +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]; + int fake_atabus=0; + + 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); + } + + struct stat statbuf; + if(check_linux_26() && 0!=stat("/sys/kernel", &statbuf)) { + static int warn_sysfs=1; + if(warn_sysfs) { + warn_sysfs=0; + fprintf(stderr, "\nWarning, sysfs is not mounted on /sys!\n" + "It is recommended to mount sysfs to allow better device configuration\n"); + sleep(5); + } + } + + if (device != NULL && *device != '\0') { + fake_atabus=0; + if(0==strncmp(device, "OLDATAPI", 8)) { + device+=3; + usalp->ops = &ata_ops; + return (SCGO_OPEN(usalp, device)); + } + else if(0==strncmp(device, "ATAPI", 5)) { + if(check_linux_26()) { + device+=5; + fake_atabus=1; + fprintf(stderr, "WARNING: the ATAPI: method is considered deprecated on modern kernels!\n" + "Mapping device specification to ATA: method now.\n" + "To force the old ATAPI: method, replace ATAPI: with OLDATAPI:\n"); + } + else { + usalp->ops = &ata_ops; + return (SCGO_OPEN(usalp, device)); + } + } + else if(0==strncmp(device, "ATA", 3)) { + fprintf(stderr, "WARNING: the ATA: method is considered deprecated on modern kernels!\n" + "Use --devices to display the native names.\n"); + fake_atabus=1; + device+=3; + } + if(device[0]==':') + device++; + + } + else if( ! in_scanmode ) { + fprintf(stderr, "WARNING: the deprecated pseudo SCSI syntax found as device specification.\n" + "Support for that may cease in the future versions of wodim. For now,\n" + "the device will be mapped to a block device file where possible.\n" + "Run \"wodim --devices\" for details.\n" ); + sleep(5); + } + + 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; + 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; + usallocal(usalp)->filenames[b][t][l] = NULL; + } + } + } + } + + if (device != NULL && *device != '\0') + { + /* open ONE directly */ + b = -1; + if (device && strncmp(device, "/dev/hd", 7) == 0 && device[8]=='\0') { + b = device[7] - 'a'; + if (b < 0 || b > 25) + b = -1; + } + if(b>=0 && fake_atabus) + b+=1000; + + f = sg_open_excl(device, O_RDWR | O_NONBLOCK, FALSE); + + 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); + /* get some fake SCSI data */ + sg_mapdev(usalp, f, &busno, &tgt, &tlun, 0, 0, b); + usal_settarget(usalp, busno, tgt, tlun); + if (sg_setup(usalp, f, busno, tgt, tlun, b, device)) + return (++nopen); + } + else { + /* scan and maybe keep one open, sg_setup decides */ +#define HDX 0 +#define SCD 1 +#define SG 2 + int h; +/* +retry_scan_open: +*/ + for(h=HDX; h <= (fake_atabus ? HDX : SG) ; h++) { + char *pattern = NULL; + unsigned int first = 0, last = 0; + switch(h) { + case(HDX): + { + pattern="/dev/hd%c"; + first='a'; + last='z'; + break; + } + case(SCD): + { + if(!check_linux_26()) + continue; + pattern="/dev/scd%d"; + first=0; + last=255; + break; + } + case(SG): + { + if(check_linux_26()) + continue; +#if 0 + /* + * Don't touch it on 2.6 until we have a proper locking scheme + */ + if(nopen<=0) + fprintf(stderr, "Warning, using /dev/sg* for SG_IO operation. This method is considered harmful.\n"); + else if(found_scd) + continue; +#endif + pattern="/dev/sg%d"; + first=0; + last=255; + break; + } + } + for(i=first; i<=last; i++) { + snprintf(devname, sizeof (devname), pattern, i); + f = sg_open_excl(devname, O_RDWR | O_NONBLOCK, in_scanmode); + if (f < 0) { + if (usalp->errstr) + snprintf(usalp->errstr, SCSI_ERRSTR_SIZE, + "Cannot open '%s'", devname); + } else { + if(h == HDX) { // double-check the capabilities on ATAPI devices + 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 */ + + /* construct the fake bus number hint, keep it readable */ + b=-1; + if(h==HDX) { + b=i-'a'; + if(!fake_atabus) + b+=1000; + } + + /* sg_setup returns false in scan mode, true if one single target was specified and opened */ + if (sg_setup(usalp, f, busno, tgt, tlun, b, devname)) + return (++nopen); + + if (in_scanmode) + nopen++; + } + } + + if (nopen > 0 && usalp->errstr) + usalp->errstr[0] = '\0'; + + /* that's crap, should not be reached in non-scan mode. + * Let's see whether it can be mapped to an atapi + * device to emulate some old cludge's behaviour. + if(!in_scanmode && busno < 1000 && busno >=0) { + fake_atabus=1; + fprintf(stderr, "Unable to open this SCSI ID. Trying to map to old ATA syntax." + "This workaround will disappear in the near future. Fix your configuration."); + goto retry_scan_open; + } + */ + } + } + + 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)->filenames[b][t][l]) { + free(usallocal(usalp)->filenames[b][t][l]); + usallocal(usalp)->filenames[b][t][l]=NULL; + } + } + } + } + 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); +} + +/*! + * + * Return: TRUE when single target is chosen and was opened successfully, FALSE otherwise (on scans, etc). + */ + +static BOOL +sg_setup(SCSI *usalp, int f, int busno, int tgt, int tlun, int ataidx, char *origname) +{ + 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 (usallocal(usalp)->filenames[Bus][Target][Lun] == NULL) + usallocal(usalp)->filenames[Bus][Target][Lun] = strdup(origname); + + 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 (struct 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/1000) * 1000; + *tgtp = ataidx%1000; + *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 char *freadstring(char *fn, char *out, int len) { + char *ret; + FILE *fd=fopen(fn, "r"); + out[0]='\0'; + if(!fd) return NULL; + ret = fgets(out, len, fd); + fclose(fd); + return ret; +} + +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 */ + long int major, minor, i; + major=stbuf.st_rdev>>8; + minor=stbuf.st_rdev&0xFF; + if (usalp->debug > 0) + fprintf(stderr, "Looking for data for major:minor: %ld:%ld\n", major, minor); + glob_t globbuf; + memset(&globbuf, 0, sizeof(glob_t)); + /* *dev files contain the major:minor strings to compare */ + glob("/sys/class/scsi_generic/*/device/block*/queue/max_sectors_kb", GLOB_DOOFFS | GLOB_NOSORT, NULL, &globbuf); + glob("/sys/block/*/device/block*/queue/max_sectors_kb", GLOB_DOOFFS | GLOB_NOSORT | GLOB_APPEND, NULL, &globbuf); + for(i=0;i<globbuf.gl_pathc; i++) { + char *cut, *ende; + char buf[64]; + cut=strstr(globbuf.gl_pathv[i], "/device/")+4; + *cut='\0'; + freadstring(globbuf.gl_pathv[i], buf, sizeof(buf)); + if(strtol(buf, &ende, 10) == major && ende && atoi(ende) == minor) { + *cut='i'; + freadstring(globbuf.gl_pathv[i], buf, sizeof(buf)); + return(1024*atoi(buf)); + } + + } + globfree(&globbuf); + } +#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) +{ + return -1; +#if 0 + /* + * Who exactly needs this information? Just for some bitching in wodim? + * Is this an _abstraction_ layer or spam layer? + */ +#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); +#endif +} + +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); +}; + +#define HAVE_NAT_NAMES +static char * usalo_natname(SCSI *usalp, int busno, int tgt, int tlun) { + if (busno >= MAX_SCG || tgt >= MAX_TGT || tlun >= MAX_LUN) + return "BADID"; + return usallocal(usalp)->filenames[busno][tgt][tlun]; +} |