summaryrefslogtreecommitdiffstats
path: root/drivers/usb/usb_scsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/usb_scsi.c')
-rw-r--r--drivers/usb/usb_scsi.c1098
1 files changed, 1098 insertions, 0 deletions
diff --git a/drivers/usb/usb_scsi.c b/drivers/usb/usb_scsi.c
new file mode 100644
index 000000000..655045bea
--- /dev/null
+++ b/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