patch-2.3.6 linux/drivers/usb/usb_scsi.c

Next file: linux/drivers/usb/usb_scsi.h
Previous file: linux/drivers/usb/usb.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.5/linux/drivers/usb/usb_scsi.c linux/drivers/usb/usb_scsi.c
@@ -0,0 +1,1098 @@
+
+/* Driver for USB scsi like devices
+ * 
+ * (C) Michael Gee (michael@linuxspecific.com) 1999
+ *
+ * This driver is scitzoid  - it makes a USB device appear as both a SCSI device
+ * and a character device. The latter is only available if the device has an
+ * interrupt endpoint, and is used specifically to receive interrupt events.
+ *
+ * In order to support various 'strange' devices, this module supports plug in
+ * device specific filter modules, which can do their own thing when required.
+ *
+ * Further reference.
+ *	This driver is based on the 'USB Mass Storage Class' document. This
+ *	describes in detail the transformation of SCSI command blocks to the
+ *	equivalent USB control and data transfer required.
+ *	It is important to note that in a number of cases this class exhibits
+ *	class-specific exemptions from the USB specification. Notably the
+ *	usage of NAK, STALL and ACK differs from the norm, in that they are
+ *	used to communicate wait, failed and OK on SCSI commands.
+ *	Also, for certain devices, the interrupt endpoint is used to convey
+ *	status of a command.
+ *
+ *	Basically, this stuff is WIERD!!
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/malloc.h>
+
+#include <asm/spinlock.h>
+#include <linux/smp_lock.h>
+
+#include <linux/blk.h>
+#include "../scsi/scsi.h"
+#include "../scsi/hosts.h"
+#include "../scsi/sd.h"
+
+#include "usb.h"
+#include "usb_scsi.h"
+
+/* direction table (what a pain) */
+
+unsigned char us_direction[256/8] = {
+
+#include "usb_scsi_dt.c"
+
+};
+
+/*
+ * Per device data
+ */
+
+static int my_host_number;
+
+int usbscsi_debug = 1;
+
+struct us_data {
+	struct us_data	*next;			/* next device */
+	struct usb_device 	*pusb_dev;
+	struct usb_scsi_filter  *filter;		/* filter driver */
+	void 			*fdata;			/* filter data */
+	unsigned int		flags;			/* from filter initially*/
+	__u8			ep_in;			/* in endpoint */
+	__u8			ep_out;			/* out ....... */
+	__u8			ep_int;			/* interrupt . */
+	__u8			subclass;		/* as in overview */
+	__u8			protocol;		/* .............. */
+	int (*pop)(Scsi_Cmnd *);			/* protocol specific do cmd */
+	GUID(guid);					/* unique dev id */
+	struct Scsi_Host	*host;			/* our dummy host data */
+	Scsi_Host_Template	*htmplt;		/* own host template */
+	int			host_number;		/* to find us */
+	int			host_no;		/* allocated by scsi */
+	int			fixedlength;		/* expand commands */
+	Scsi_Cmnd		*srb;			/* current srb */
+	int			action;			/* what to do */
+	wait_queue_head_t	waitq;			/* thread waits */
+	wait_queue_head_t	ip_waitq;		/* for CBI interrupts */
+	__u16			ip_data;		/* interrupt data */
+	int			ip_wanted;		/* needed */
+	int			pid;			/* control thread */
+	struct semaphore      	*notify; 		/* wait for thread to begin */
+};
+
+/*
+ * kernel thread actions
+ */
+
+#define US_ACT_COMMAND		1
+#define US_ACT_ABORT		2
+#define US_ACT_DEVICE_RESET	3
+#define US_ACT_BUS_RESET	4
+#define US_ACT_HOST_RESET	5
+
+static struct proc_dir_entry proc_usb_scsi =
+{
+    PROC_SCSI_USB_SCSI,
+    0,
+    NULL,
+    S_IFDIR | S_IRUGO | S_IXUGO,
+    2
+};
+
+static struct us_data *us_list;
+
+static struct usb_scsi_filter *filters;
+
+static int scsi_probe(struct usb_device *dev);
+static void scsi_disconnect(struct usb_device *dev);
+static struct usb_driver scsi_driver = {
+	"usb_scsi",
+	scsi_probe,
+	scsi_disconnect,
+	{ NULL, NULL }
+};
+
+/* Data handling, using SG if required */
+
+static int us_one_transfer(struct us_data *us, int pipe, char *buf, int length)
+{
+    int max_size = usb_maxpacket(us->pusb_dev, pipe) * 16;
+    int this_xfer;
+    int result;
+    unsigned long partial;
+    int maxtry = 100;
+    while (length) {
+	this_xfer = length > max_size ? max_size : length;
+	length -= this_xfer;
+	do {
+	    US_DEBUGP("Bulk xfer %x(%d)\n", (unsigned int)buf, this_xfer);
+	    result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev, pipe, buf, 
+						    this_xfer, &partial);
+
+	    /* we want to retry if the device reported NAK */
+	    if (result == USB_ST_TIMEOUT) {
+		if (!maxtry--)
+		    break;
+		this_xfer -= partial;
+		buf += partial;
+	    } else if (!result && partial != this_xfer) {
+		/* short data - assume end */
+		result = USB_ST_DATAUNDERRUN;
+		break;
+	    } else
+		break;
+	} while ( this_xfer );
+	if (result)
+	    return result;
+	buf += this_xfer;
+    }
+    return 0;
+
+}
+static int us_transfer(Scsi_Cmnd *srb, int dir_in)
+{
+    struct us_data *us = (struct us_data *)srb->host_scribble;
+    int i;
+    int result = -1;
+
+    if (srb->use_sg) {
+	struct scatterlist *sg = (struct scatterlist *) srb->request_buffer;
+
+	for (i = 0; i < srb->use_sg; i++) {
+	    result = us_one_transfer(us, dir_in ? usb_rcvbulkpipe(us->pusb_dev, us->ep_in) :
+						  usb_sndbulkpipe(us->pusb_dev, us->ep_out),
+				     sg[i].address, sg[i].length);
+	    if (result)
+		break;
+	}
+	return result;
+    }
+    else
+	return us_one_transfer(us, dir_in ? usb_rcvbulkpipe(us->pusb_dev, us->ep_in) :
+					    usb_sndbulkpipe(us->pusb_dev, us->ep_out),
+			       srb->request_buffer, srb->request_bufflen);
+}
+
+static unsigned int us_transfer_length(Scsi_Cmnd *srb)
+{
+    int i;
+    unsigned int total = 0;
+
+    /* always zero for some commands */
+    switch (srb->cmnd[0]) {
+    case SEEK_6:
+    case SEEK_10:
+    case REZERO_UNIT:
+    case ALLOW_MEDIUM_REMOVAL:
+    case START_STOP:
+    case TEST_UNIT_READY:
+	return 0;
+
+    default:
+	break;
+    }
+
+    if (srb->use_sg) {
+	struct scatterlist *sg = (struct scatterlist *) srb->request_buffer;
+
+	for (i = 0; i < srb->use_sg; i++) {
+	    total += sg[i].length;
+	}
+	return total;
+    }
+    else
+	return srb->request_bufflen;
+
+}
+
+static int pop_CBI_irq(int state, void *buffer, void *dev_id)
+{
+    struct us_data *us = (struct us_data *)dev_id;
+
+    if (state != USB_ST_REMOVED) {
+	us->ip_data = *(__u16 *)buffer;
+	us->ip_wanted = 0;
+    }
+    wake_up(&us->ip_waitq);
+
+    /* we dont want another interrupt */
+
+    return 0;
+}
+static int pop_CB_command(Scsi_Cmnd *srb)
+{
+    struct us_data *us = (struct us_data *)srb->host_scribble;
+    devrequest dr;
+    unsigned char cmd[16];
+    int result;
+    int retry = 1;
+    int done_start = 0;
+
+    while (retry--) {
+	dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE;
+	dr.request = US_CBI_ADSC;
+	dr.value = 0;
+	dr.index = us->pusb_dev->ifnum;
+	dr.length = srb->cmd_len;
+    
+	if (us->flags & US_FL_FIXED_COMMAND) {
+	    dr.length = us->fixedlength;
+	    memset(cmd, 0, us->fixedlength);
+
+	    /* fix some commands */
+
+	    switch (srb->cmnd[0]) {
+	    case WRITE_6:
+	    case READ_6:
+		cmd[0] = srb->cmnd[0] | 0x20;
+		cmd[1] = srb->cmnd[1] & 0xE0;
+		cmd[2] = 0;
+		cmd[3] = srb->cmnd[1] & 0x1F;
+		cmd[4] = srb->cmnd[2];
+		cmd[5] = srb->cmnd[3];
+		cmd[8] = srb->cmnd[4];
+		break;
+
+	    case MODE_SENSE:
+	    case MODE_SELECT:
+		cmd[0] = srb->cmnd[0] | 0x40;
+		cmd[1] = srb->cmnd[1];
+		cmd[2] = srb->cmnd[2];
+		cmd[8] = srb->cmnd[4];
+		break;
+
+	    default:
+		memcpy(cmd, srb->cmnd, srb->cmd_len);
+		break;
+	    }
+	    result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, 
+						  usb_sndctrlpipe(us->pusb_dev,0),
+						  &dr, cmd, us->fixedlength);
+	    if (!done_start && us->subclass == US_SC_UFI && cmd[0] == TEST_UNIT_READY && result) {
+		/* as per spec try a start command, wait and retry */
+
+		done_start++;
+		cmd[0] = START_STOP;
+		cmd[4] = 1;		/* start */
+		result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, 
+						      usb_sndctrlpipe(us->pusb_dev,0),
+						      &dr, cmd, us->fixedlength);
+		wait_ms(100);
+		retry++;
+		continue;
+	    }
+	} else
+	    result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, 
+						  usb_sndctrlpipe(us->pusb_dev,0),
+						  &dr, srb->cmnd, srb->cmd_len);
+	if (result != USB_ST_STALL && result != USB_ST_TIMEOUT)
+	    return result;
+    }
+    return result;
+}
+
+/* Protocol command handlers */
+
+static int pop_CBI(Scsi_Cmnd *srb)
+{
+    struct us_data *us = (struct us_data *)srb->host_scribble;
+    int result;
+
+    /* run the command */
+
+    if ((result = pop_CB_command(srb))) {
+	US_DEBUGP("CBI command %x\n", result);
+	if (result == USB_ST_STALL || result == USB_ST_TIMEOUT)
+	    return (DID_OK << 16) | 2;
+	return DID_ABORT << 16;
+    }
+
+    /* transfer the data */
+
+    if (us_transfer_length(srb)) {
+	result = us_transfer(srb, US_DIRECTION(srb->cmnd[0]));
+	if (result && result != USB_ST_DATAUNDERRUN) {
+	    US_DEBUGP("CBI  transfer %x\n", result);
+	    return DID_ABORT << 16;
+	}
+    }
+
+    /* get status */
+
+    if (us->protocol == US_PR_CBI) {
+	/* get from interrupt pipe */
+
+	/* add interrupt transfer, marked for removal */
+	us->ip_wanted = 1;
+	result = us->pusb_dev->bus->op->request_irq(us->pusb_dev, 
+						    usb_rcvctrlpipe(us->pusb_dev, us->ep_int),
+						     pop_CBI_irq, 0, (void *)us);
+	if (result) {
+	    US_DEBUGP("No interrupt for CBI %x\n", result);
+	    return DID_ABORT << 16;
+	}
+	sleep_on(&us->ip_waitq);
+	if (us->ip_wanted) {
+	    US_DEBUGP("Did not get interrupt on CBI\n");
+	    us->ip_wanted = 0;
+	    return DID_ABORT << 16;
+	}
+
+	US_DEBUGP("Got interrupt data %x\n", us->ip_data);
+
+	/* sort out what it means */
+
+	if (us->subclass == US_SC_UFI) {
+	    /* gives us asc and ascq, as per request sense */
+
+	    if (srb->cmnd[0] == REQUEST_SENSE ||
+		srb->cmnd[0] == INQUIRY)
+		return DID_OK << 16;
+	    else
+		return (DID_OK << 16) + ((us->ip_data & 0xff) ? 2 : 0);
+	}
+	if (us->ip_data & 0xff) {
+	    US_DEBUGP("Bad CBI interrupt data %x\n", us->ip_data);
+	    return DID_ABORT << 16;
+	}
+	return (DID_OK << 16) + ((us->ip_data & 0x300) ? 2 : 0);
+    } else {
+       /* get from where? */
+    }
+    return DID_ERROR << 16;
+}
+
+static int pop_Bulk_reset(struct us_data *us)
+{
+    devrequest dr;
+    int result;
+
+    dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE;
+    dr.request = US_BULK_RESET;
+    dr.value = US_BULK_RESET_SOFT;
+    dr.index = 0;
+    dr.length = 0;
+
+    US_DEBUGP("Bulk soft reset\n");
+    result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, usb_sndctrlpipe(us->pusb_dev,0), &dr, NULL, 0);
+    if (result) {
+	US_DEBUGP("Bulk soft reset failed %d\n", result);
+	dr.value = US_BULK_RESET_HARD;
+	result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, usb_sndctrlpipe(us->pusb_dev,0), &dr, NULL, 0);
+	if (result)
+	    US_DEBUGP("Bulk hard reset failed %d\n", result);
+    }
+    usb_clear_halt(us->pusb_dev, us->ep_in | 0x80);
+    usb_clear_halt(us->pusb_dev, us->ep_out);
+    return result;
+}
+/*
+ * The bulk only protocol handler.
+ * 	Uses the in and out endpoints to transfer commands and data (nasty)
+ */
+static int pop_Bulk(Scsi_Cmnd *srb)
+{
+    struct us_data *us = (struct us_data *)srb->host_scribble;
+    struct bulk_cb_wrap bcb;
+    struct bulk_cs_wrap bcs;
+    int result;
+    unsigned long partial;
+    int stall;
+
+    /* set up the command wrapper */
+
+    bcb.Signature = US_BULK_CB_SIGN;
+    bcb.DataTransferLength = us_transfer_length(srb);;
+    bcb.Flags = US_DIRECTION(srb->cmnd[0]) << 7;
+    bcb.Tag = srb->serial_number;
+    bcb.Lun = 0;
+    memset(bcb.CDB, 0, sizeof(bcb.CDB));
+    memcpy(bcb.CDB, srb->cmnd, srb->cmd_len);
+    if (us->flags & US_FL_FIXED_COMMAND) {
+	bcb.Length = us->fixedlength;
+    } else {
+	bcb.Length = srb->cmd_len;
+    }
+
+    /* send it to out endpoint */
+
+    US_DEBUGP("Bulk command S %x T %x L %d F %d CL %d\n", bcb.Signature,
+	      bcb.Tag, bcb.DataTransferLength, bcb.Flags, bcb.Length);
+    result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev,
+		     usb_sndbulkpipe(us->pusb_dev, us->ep_out), &bcb, 
+					    US_BULK_CB_WRAP_LEN, &partial);
+    if (result) {
+	US_DEBUGP("Bulk command result %x\n", result);
+	return DID_ABORT << 16;
+    }
+
+    //return DID_BAD_TARGET << 16;
+    /* send/receive data */
+
+    if (bcb.DataTransferLength) {
+	result = us_transfer(srb, bcb.Flags);
+	if (result && result != USB_ST_DATAUNDERRUN && result != USB_ST_STALL) {
+	    US_DEBUGP("Bulk transfer result %x\n", result);
+	    return DID_ABORT << 16;
+	}
+    }
+
+    /* get status */
+
+
+    stall = 0;
+    do {
+	//usb_settoggle(us->pusb_dev, us->ep_in, 0);	/* AAARgh!! */
+	US_DEBUGP("Toggle is %d\n", usb_gettoggle(us->pusb_dev, us->ep_in));
+	result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev,
+			 usb_rcvbulkpipe(us->pusb_dev, us->ep_in), &bcs, 
+						US_BULK_CS_WRAP_LEN, &partial);
+	if (result == USB_ST_STALL || result == USB_ST_TIMEOUT)
+	    stall++;
+	else
+	    break;
+    } while ( stall < 3);
+    if (result && result != USB_ST_DATAUNDERRUN) {
+	US_DEBUGP("Bulk status result = %x\n", result);
+	return DID_ABORT << 16;
+    }
+
+    /* check bulk status */
+
+    US_DEBUGP("Bulk status S %x T %x R %d V %x\n", bcs.Signature, bcs.Tag,
+	      bcs.Residue, bcs.Status);
+    if (bcs.Signature != US_BULK_CS_SIGN || bcs.Tag != bcb.Tag ||
+	bcs.Status > US_BULK_STAT_PHASE) {
+	US_DEBUGP("Bulk logical error\n");
+	return DID_ABORT << 16;
+    }
+    switch (bcs.Status) {
+    case US_BULK_STAT_OK:
+	return DID_OK << 16;
+
+    case US_BULK_STAT_FAIL:
+	/* check for underrun - dont report */
+	if (bcs.Residue)
+	    return DID_OK << 16;
+	//pop_Bulk_reset(us);
+	break;
+
+    case US_BULK_STAT_PHASE:
+	return DID_ERROR << 16;
+    }
+    return (DID_OK << 16) | 2;		/* check sense required */
+
+}
+
+/* Host functions */
+
+/* detect adapter (always true ) */
+static int us_detect(struct SHT *sht)
+{
+    /* FIXME - not nice at all, but how else ? */
+    struct us_data *us = (struct us_data *)sht->proc_dir;
+    char name[32];
+
+    sprintf(name, "usbscsi%d", us->host_number);
+    proc_usb_scsi.namelen = strlen(name);
+    proc_usb_scsi.name = kmalloc(proc_usb_scsi.namelen+1, GFP_KERNEL);
+    if (!proc_usb_scsi.name)
+	return 0;
+    strcpy((char *)proc_usb_scsi.name, name);
+    sht->proc_dir = kmalloc(sizeof(*sht->proc_dir), GFP_KERNEL);
+    if (!sht->proc_dir) {
+	kfree(proc_usb_scsi.name);
+	return 0;
+    }
+    *sht->proc_dir = proc_usb_scsi;
+    sht->name = proc_usb_scsi.name;
+    us->host = scsi_register(sht, sizeof(us));
+    if (us->host) {
+	us->host->hostdata[0] = (unsigned long)us;
+	us->host_no = us->host->host_no;
+	return 1;
+    }
+    kfree(proc_usb_scsi.name);
+    kfree(sht->proc_dir);
+    return 0;
+}
+
+/* release - must be here to stop scsi
+ *	from trying to release IRQ etc.
+ *	Kill off our data
+ */
+static int us_release(struct Scsi_Host *psh)
+{
+    struct us_data *us = (struct us_data *)psh->hostdata[0];
+    struct us_data *prev = (struct us_data *)&us_list;
+
+    if (us->filter)
+	us->filter->release(us->fdata);
+    if (us->pusb_dev)
+	usb_deregister(&scsi_driver);
+
+    /* FIXME - leaves hanging host template copy */
+    /* (bacause scsi layer uses it after removal !!!) */
+    while(prev->next != us)
+	prev = prev->next;
+    prev->next = us->next;
+    return 0;
+}
+
+/* run command */
+static int us_command( Scsi_Cmnd *srb )
+{
+    US_DEBUGP("Bad use of us_command\n");
+
+    return DID_BAD_TARGET << 16;
+}
+
+/* run command */
+static int us_queuecommand( Scsi_Cmnd *srb , void (*done)(Scsi_Cmnd *))
+{
+    struct us_data *us = (struct us_data *)srb->host->hostdata[0];
+
+    US_DEBUGP("Command wakeup\n");
+    srb->host_scribble = (unsigned char *)us;
+    us->srb = srb;
+    srb->scsi_done = done;
+    us->action = US_ACT_COMMAND;
+
+    /* wake up the process task */
+
+    wake_up_interruptible(&us->waitq);
+
+    return 0;
+}
+
+static int us_abort( Scsi_Cmnd *srb )
+{
+    return 0;
+}
+
+static int us_device_reset( Scsi_Cmnd *srb )
+{
+    return 0;
+}
+
+static int us_host_reset( Scsi_Cmnd *srb )
+{
+    return 0;
+}
+
+static int us_bus_reset( Scsi_Cmnd *srb )
+{
+    return 0;
+}
+
+#undef SPRINTF
+#define SPRINTF(args...) { if (pos < (buffer + length)) pos += sprintf (pos, ## args); }
+
+int usb_scsi_proc_info (char *buffer, char **start, off_t offset, int length, int hostno, int inout)
+{
+    struct us_data *us = us_list;
+    char *pos = buffer;
+    char *vendor;
+    char *product;
+    char *style = "";
+
+    /* find our data from hostno */
+
+    while (us) {
+	if (us->host_no == hostno)
+	    break;
+	us = us->next;
+    }
+
+    if (!us)
+	return -ESRCH;
+
+    /* null on outward */
+
+    if (inout)
+	return length;
+
+    if (!(vendor = usb_string(us->pusb_dev, us->pusb_dev->descriptor.iManufacturer)))
+	vendor = "?";
+    if (!(product = usb_string(us->pusb_dev, us->pusb_dev->descriptor.iProduct)))
+	product = "?";
+
+    switch (us->protocol) {
+    case US_PR_CB:
+	style = "Control/Bulk";
+	break;
+
+    case US_PR_CBI:
+	style = "Control/Bulk/Interrupt";
+	break;
+
+    case US_PR_ZIP:
+	style = "Bulk only";
+	break;
+
+    }
+    SPRINTF ("Host scsi%d: usb-scsi\n", hostno);
+    SPRINTF ("Device: %s %s - GUID " GUID_FORMAT "\n", vendor, product, GUID_ARGS(us->guid) );
+    SPRINTF ("Style: %s\n", style);
+
+    /*
+     * Calculate start of next buffer, and return value.
+     */
+    *start = buffer + offset;
+
+    if ((pos - buffer) < offset)
+	return (0);
+    else if ((pos - buffer - offset) < length)
+	return (pos - buffer - offset);
+    else
+	return (length);
+}
+
+/*
+ * this defines our 'host'
+ */
+
+static Scsi_Host_Template my_host_template = {
+    NULL,			/* next */
+    NULL,			/* module */
+    NULL,			/* proc_dir */
+    usb_scsi_proc_info,
+    NULL,			/* name - points to unique */
+    us_detect,
+    us_release,
+    NULL,			/* info */
+    NULL,			/* ioctl */
+    us_command,
+    us_queuecommand,
+    NULL,			/* eh_strategy */
+    us_abort,
+    us_device_reset,
+    us_bus_reset,
+    us_host_reset,
+    NULL,			/* abort */
+    NULL,			/* reset */
+    NULL,			/* slave_attach */
+    NULL,			/* bios_param */
+    1,				/* can_queue */
+    -1,				/* this_id */
+    SG_ALL,			/* sg_tablesize */
+    1,				/* cmd_per_lun */
+    0,				/* present */
+    FALSE,			/* unchecked_isa_dma */
+    FALSE,			/* use_clustering */
+    TRUE,			/* use_new_eh_code */
+    TRUE			/* emulated */
+};
+
+static int usbscsi_control_thread(void * __us)
+{
+    struct us_data *us = (struct us_data *)__us;
+    int action;
+
+    lock_kernel();
+
+    /*
+     * This thread doesn't need any user-level access,
+     * so get rid of all our resources..
+     */
+    exit_mm(current);
+    exit_files(current);
+    //exit_fs(current);
+
+    sprintf(current->comm, "usbscsi%d", us->host_no);
+    
+    unlock_kernel();
+
+    up(us->notify);
+
+    for(;;) {
+	    siginfo_t info;
+	    int unsigned long signr;
+
+	    interruptible_sleep_on(&us->waitq);
+
+	    action = us->action;
+	    us->action = 0;
+
+	    switch (action) {
+	    case US_ACT_COMMAND       :
+		if (!us->pusb_dev || us->srb->target || us->srb->lun) {
+		    /* bad device */
+		    US_DEBUGP( "Bad device number (%d/%d) or dev %x\n", us->srb->target, us->srb->lun, (unsigned int)us->pusb_dev);
+		    us->srb->result = DID_BAD_TARGET << 16;
+		} else {
+		    US_DEBUG(us_show_command(us->srb));
+		    if (us->filter && us->filter->command)
+		        us->srb->result = us->filter->command(us->fdata, us->srb);
+		    else
+		        us->srb->result = us->pop(us->srb);
+		}
+		us->srb->scsi_done(us->srb);
+		break;
+
+	    case US_ACT_ABORT         :
+		break;
+
+	    case US_ACT_DEVICE_RESET  :
+		break;
+
+	    case US_ACT_BUS_RESET     :
+		break;
+
+	    case US_ACT_HOST_RESET    :
+		break;
+
+	    }
+
+	    if(signal_pending(current)) {
+		    /* sending SIGUSR1 makes us print out some info */
+		    spin_lock_irq(&current->sigmask_lock);
+		    signr = dequeue_signal(&current->blocked, &info);
+		    spin_unlock_irq(&current->sigmask_lock);
+
+		    if (signr == SIGUSR2) {
+			    printk("USBSCSI debug toggle\n");
+			    usbscsi_debug = !usbscsi_debug;
+		    } else {
+			    break;
+		    }
+	    }
+    }
+
+    MOD_DEC_USE_COUNT;
+
+    printk("usbscsi_control_thread exiting\n");
+
+    return 0;
+}	
+
+static int scsi_probe(struct usb_device *dev)
+{
+    struct usb_interface_descriptor *interface;
+    int i;
+    char *mf;		/* manufacturer */
+    char *prod;		/* product */
+    char *serial;		/* serial number */
+    struct us_data *ss = NULL;
+    struct usb_scsi_filter *filter = filters;
+    void *fdata = NULL;
+    unsigned int flags = 0;
+    GUID(guid);
+    struct us_data *prev;
+    Scsi_Host_Template *htmplt;
+    int protocol = 0;
+    int subclass = 0;
+
+    GUID_CLEAR(guid);
+    mf = usb_string(dev, dev->descriptor.iManufacturer);
+    prod = usb_string(dev, dev->descriptor.iProduct);
+    serial = usb_string(dev, dev->descriptor.iSerialNumber);
+
+    /* probe with filters first */
+
+    if (mf && prod)	{
+	    while (filter) {
+		    if ((fdata = filter->probe(dev, mf, prod, serial)) != NULL) {
+			    flags = filter->flags;
+			    printk(KERN_INFO "USB Scsi filter %s\n", filter->name);
+			    break;
+		    }
+		    filter = filter->next;
+	    }
+    }
+
+    /* generic devices next */
+
+    if (fdata == NULL) {
+
+	/* some exceptions */
+	if (dev->descriptor.idVendor == 0x04e6 &&
+	    dev->descriptor.idProduct == 0x0001) {
+	    /* shuttle E-USB */
+	    protocol = US_PR_ZIP;
+	    subclass = US_SC_8070;	/* an assumption */
+	} else if (dev->descriptor.bDeviceClass != 0 ||
+	    dev->config->altsetting->interface->bInterfaceClass != 8 ||
+	    dev->config->altsetting->interface->bInterfaceSubClass < US_SC_MIN ||
+	    dev->config->altsetting->interface->bInterfaceSubClass > US_SC_MAX) {
+	    return -1;
+	}
+
+	/* now check if we have seen it before */
+
+	if (dev->descriptor.iSerialNumber && 
+	    usb_string(dev, dev->descriptor.iSerialNumber) ) {
+	    make_guid(guid, dev->descriptor.idVendor, dev->descriptor.idProduct,
+		      usb_string(dev, dev->descriptor.iSerialNumber));
+	    for (ss = us_list; ss; ss = ss->next) {
+		if (GUID_EQUAL(guid, ss->guid))	{
+		    US_DEBUGP("Found existing GUID " GUID_FORMAT "\n", GUID_ARGS(guid));
+		    break;
+		}
+	    }
+	}
+    }
+
+    if (!ss) {
+	if ((ss = (struct us_data *)kmalloc(sizeof(*ss), GFP_KERNEL)) == NULL) {
+	    printk(KERN_WARNING USB_SCSI "Out of memory\n");
+	    if (filter)
+		    filter->release(fdata);
+	    return -1;
+	}
+	memset(ss, 0, sizeof(struct us_data));
+    }
+
+    interface = dev->config->altsetting->interface;
+    ss->filter = filter;
+    ss->fdata = fdata;
+    ss->flags = flags;
+    if (subclass) {
+	ss->subclass = subclass;
+	ss->protocol = protocol;
+    } else {
+	ss->subclass = interface->bInterfaceSubClass;
+	ss->protocol = interface->bInterfaceProtocol;
+    }
+
+    /* set the protocol op */
+
+    US_DEBUGP("Protocol ");
+    switch (ss->protocol) {
+    case US_PR_CB:
+	US_DEBUGPX("Control/Bulk\n");
+	ss->pop = pop_CBI;
+	break;
+
+    case US_PR_CBI:
+	US_DEBUGPX("Control/Bulk/Interrupt\n");
+	ss->pop = pop_CBI;
+	break;
+
+    default:
+	US_DEBUGPX("Bulk\n");
+	ss->pop = pop_Bulk;
+	break;
+    }
+
+    /* 
+     * we are expecting a minimum of 2 endpoints - in and out (bulk)
+     * an optional interrupt is OK (necessary for CBI protocol)
+     * we will ignore any others
+     */
+
+    for (i = 0; i < interface->bNumEndpoints; i++) { 
+	    if (interface->endpoint[i].bmAttributes == 0x02) {
+		    if (interface->endpoint[i].bEndpointAddress & 0x80)
+			    ss->ep_in = interface->endpoint[i].bEndpointAddress & 0x0f;
+		    else
+			    ss->ep_out = interface->endpoint[i].bEndpointAddress & 0x0f;
+	    } else if (interface->endpoint[i].bmAttributes == 0x03) {
+		    ss->ep_int = interface->endpoint[i].bEndpointAddress & 0x0f;
+	    }
+    }
+    US_DEBUGP("Endpoints In %d Out %d Int %d\n", ss->ep_in, ss->ep_out, ss->ep_int);
+
+    /* exit if strange looking */
+
+    if (usb_set_configuration(dev, dev->config[0].bConfigurationValue) ||
+	!ss->ep_in || !ss->ep_out || (ss->protocol == US_PR_CBI && ss->ep_int == 0)) {
+	US_DEBUGP("Problems with device\n");
+	if (ss->host) {
+	    scsi_unregister_module(MODULE_SCSI_HA, ss->htmplt);
+	    kfree(ss->htmplt->name);
+	    kfree(ss->htmplt);
+	}
+	if (filter)
+		filter->release(fdata);
+	kfree(ss);
+	return -1;			/* no endpoints */
+    }
+
+    if (dev->config[0].iConfiguration && usb_string(dev, dev->config[0].iConfiguration))
+	US_DEBUGP("Configuration %s\n", usb_string(dev, dev->config[0].iConfiguration));
+    if (interface->iInterface && usb_string(dev, interface->iInterface))
+	US_DEBUGP("Interface %s\n", usb_string(dev, interface->iInterface));
+
+    ss->pusb_dev = dev;
+
+    /* Now generate a scsi host definition, and register with scsi above us */
+
+    if (!ss->host) {
+
+	/* make unique id if possible */
+
+	if (dev->descriptor.iSerialNumber && 
+	    usb_string(dev, dev->descriptor.iSerialNumber) ) {
+	    make_guid(ss->guid, dev->descriptor.idVendor, dev->descriptor.idProduct,
+		      usb_string(dev, dev->descriptor.iSerialNumber));
+	}
+
+	US_DEBUGP("New GUID " GUID_FORMAT "\n", GUID_ARGS(guid));
+
+	/* set class specific stuff */
+
+	US_DEBUGP("SubClass ");
+	switch (ss->subclass) {
+	case US_SC_RBC:
+	    US_DEBUGPX("Reduced Block Commands\n");
+	    break;
+	case US_SC_8020:
+	    US_DEBUGPX("8020\n");
+	    break;
+	case US_SC_QIC:
+	    US_DEBUGPX("QIC157\n");
+	    break;
+	case US_SC_8070:
+	    US_DEBUGPX("8070\n");
+	    ss->flags |= US_FL_FIXED_COMMAND;
+	    ss->fixedlength = 12;
+	    break;
+	case US_SC_SCSI:
+	    US_DEBUGPX("Transparent SCSI\n");
+	    break;
+	case US_SC_UFI:
+	    US_DEBUGPX(" UFF\n");
+	    ss->flags |= US_FL_FIXED_COMMAND;
+	    ss->fixedlength = 12;
+	    break;
+
+	default:
+	    break;
+	}
+
+	/* create unique host template */
+
+	if ((htmplt = (Scsi_Host_Template *)kmalloc(sizeof(*ss->htmplt), GFP_KERNEL)) == NULL ) {
+	    printk(KERN_WARNING USB_SCSI "Out of memory\n");
+	    if (filter)
+		    filter->release(fdata);
+	    kfree(ss);
+	    return -1;
+	}
+	memcpy(htmplt, &my_host_template, sizeof(my_host_template));
+	ss->host_number = my_host_number++;
+
+
+	(struct us_data *)htmplt->proc_dir = ss;
+	if (ss->protocol == US_PR_CBI)
+	    init_waitqueue_head(&ss->ip_waitq);
+
+	/* start up our thread */
+
+	{
+	    DECLARE_MUTEX_LOCKED(sem);
+
+	    init_waitqueue_head(&ss->waitq);
+    
+	    ss->notify = &sem;
+	    ss->pid = kernel_thread(usbscsi_control_thread, ss,
+		    CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+	    if (ss->pid < 0) {
+		printk(KERN_WARNING USB_SCSI "Unable to start control thread\n");
+		kfree(htmplt);
+		if (filter)
+			filter->release(fdata);
+		kfree(ss);
+		return -1;
+	    }
+
+	    /* wait for it to start */
+
+	    down(&sem);
+	}
+
+	/* now register - our detect function will be called */
+
+	scsi_register_module(MODULE_SCSI_HA, htmplt);
+
+	/* put us in the list */
+
+	prev = (struct us_data *)&us_list;
+	while (prev->next)
+	    prev = prev->next;
+	prev->next = ss;
+
+    }
+
+
+    printk(KERN_INFO "USB SCSI device found at address %d\n", dev->devnum);
+
+    dev->private = ss;
+    return 0;
+}
+
+static void scsi_disconnect(struct usb_device *dev)
+{
+	struct us_data *ss = dev->private;
+
+	if (!ss)
+	    return;
+	if (ss->filter)
+		ss->filter->release(ss->fdata);
+	ss->pusb_dev = NULL;
+	dev->private = NULL;		/* just in case */
+	MOD_DEC_USE_COUNT;
+}
+
+int usb_scsi_init(void)
+{
+
+	MOD_INC_USE_COUNT;
+#ifdef CONFIG_USB_HP4100
+	hp4100_init();
+#endif
+#ifdef CONFIG_USB_ZIP
+	usb_zip_init();
+#endif
+	usb_register(&scsi_driver);
+	printk(KERN_INFO "USB SCSI support registered.\n");
+	return 0;
+}
+
+
+int usb_scsi_register(struct usb_scsi_filter *filter)
+{
+	struct usb_scsi_filter *prev = (struct usb_scsi_filter *)&filters;
+
+	while (prev->next)
+		prev = prev->next;
+	prev->next = filter;
+	return 0;
+}
+
+void usb_scsi_deregister(struct usb_scsi_filter *filter)
+{
+	struct usb_scsi_filter *prev = (struct usb_scsi_filter *)&filters;
+
+	while (prev->next && prev->next != filter)
+		prev = prev->next;
+	if (prev->next)
+		prev->next = filter->next;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+
+	return usb_scsi_init();
+}
+
+void cleanup_module(void)
+{
+	unsigned int offset;
+
+	usb_deregister(&scsi_driver);
+}
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)