diff options
Diffstat (limited to 'drivers/usb/usb_scsi.c')
-rw-r--r-- | drivers/usb/usb_scsi.c | 1098 |
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(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->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 |