diff options
Diffstat (limited to 'drivers/usb/storage/uas.c')
| -rw-r--r-- | drivers/usb/storage/uas.c | 1059 | 
1 files changed, 786 insertions, 273 deletions
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index d1268191acb..511b2295316 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -2,6 +2,7 @@   * USB Attached SCSI   * Note that this is not the same as the USB Mass Storage driver   * + * Copyright Hans de Goede <hdegoede@redhat.com> for Red Hat, Inc. 2013   * Copyright Matthew Wilcox for Intel Corp, 2010   * Copyright Sarah Sharp for Intel Corp, 2010   * @@ -11,55 +12,22 @@  #include <linux/blkdev.h>  #include <linux/slab.h>  #include <linux/types.h> +#include <linux/module.h>  #include <linux/usb.h> +#include <linux/usb_usual.h> +#include <linux/usb/hcd.h>  #include <linux/usb/storage.h> +#include <linux/usb/uas.h>  #include <scsi/scsi.h> +#include <scsi/scsi_eh.h>  #include <scsi/scsi_dbg.h>  #include <scsi/scsi_cmnd.h>  #include <scsi/scsi_device.h>  #include <scsi/scsi_host.h>  #include <scsi/scsi_tcq.h> -/* Common header for all IUs */ -struct iu { -	__u8 iu_id; -	__u8 rsvd1; -	__be16 tag; -}; - -enum { -	IU_ID_COMMAND		= 0x01, -	IU_ID_STATUS		= 0x03, -	IU_ID_RESPONSE		= 0x04, -	IU_ID_TASK_MGMT		= 0x05, -	IU_ID_READ_READY	= 0x06, -	IU_ID_WRITE_READY	= 0x07, -}; - -struct command_iu { -	__u8 iu_id; -	__u8 rsvd1; -	__be16 tag; -	__u8 prio_attr; -	__u8 rsvd5; -	__u8 len; -	__u8 rsvd7; -	struct scsi_lun lun; -	__u8 cdb[16];	/* XXX: Overflow-checking tools may misunderstand */ -}; - -struct sense_iu { -	__u8 iu_id; -	__u8 rsvd1; -	__be16 tag; -	__be16 status_qual; -	__u8 status; -	__u8 service_response; -	__u8 rsvd8[6]; -	__be16 len; -	__u8 sense[SCSI_SENSE_BUFFERSIZE]; -}; +#include "uas-detect.h"  /*   * The r00-r01c specs define this version of the SENSE IU data structure. @@ -75,36 +43,41 @@ struct sense_iu_old {  	__u8 sense[SCSI_SENSE_BUFFERSIZE];  }; -enum { -	CMD_PIPE_ID		= 1, -	STATUS_PIPE_ID		= 2, -	DATA_IN_PIPE_ID		= 3, -	DATA_OUT_PIPE_ID	= 4, - -	UAS_SIMPLE_TAG		= 0, -	UAS_HEAD_TAG		= 1, -	UAS_ORDERED_TAG		= 2, -	UAS_ACA			= 4, -}; -  struct uas_dev_info {  	struct usb_interface *intf;  	struct usb_device *udev; -	int qdepth; +	struct usb_anchor cmd_urbs; +	struct usb_anchor sense_urbs; +	struct usb_anchor data_urbs; +	int qdepth, resetting; +	struct response_iu response;  	unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe;  	unsigned use_streams:1;  	unsigned uas_sense_old:1; +	unsigned running_task:1; +	unsigned shutdown:1; +	struct scsi_cmnd *cmnd; +	spinlock_t lock; +	struct work_struct work; +	struct list_head inflight_list; +	struct list_head dead_list;  };  enum { -	ALLOC_SENSE_URB		= (1 << 0), -	SUBMIT_SENSE_URB	= (1 << 1), +	SUBMIT_STATUS_URB	= (1 << 1),  	ALLOC_DATA_IN_URB	= (1 << 2),  	SUBMIT_DATA_IN_URB	= (1 << 3),  	ALLOC_DATA_OUT_URB	= (1 << 4),  	SUBMIT_DATA_OUT_URB	= (1 << 5),  	ALLOC_CMD_URB		= (1 << 6),  	SUBMIT_CMD_URB		= (1 << 7), +	COMMAND_INFLIGHT        = (1 << 8), +	DATA_IN_URB_INFLIGHT    = (1 << 9), +	DATA_OUT_URB_INFLIGHT   = (1 << 10), +	COMMAND_COMPLETED       = (1 << 11), +	COMMAND_ABORTED         = (1 << 12), +	UNLINK_DATA_URBS        = (1 << 13), +	IS_IN_WORK_LIST         = (1 << 14),  };  /* Overrides scsi_pointer */ @@ -112,7 +85,6 @@ struct uas_cmd_info {  	unsigned int state;  	unsigned int stream;  	struct urb *cmd_urb; -	struct urb *sense_urb;  	struct urb *data_in_urb;  	struct urb *data_out_urb;  	struct list_head list; @@ -121,28 +93,121 @@ struct uas_cmd_info {  /* I hate forward declarations, but I actually have a loop */  static int uas_submit_urbs(struct scsi_cmnd *cmnd,  				struct uas_dev_info *devinfo, gfp_t gfp); +static void uas_do_work(struct work_struct *work); +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller); +static void uas_free_streams(struct uas_dev_info *devinfo); +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller); + +/* Must be called with devinfo->lock held, will temporary unlock the lock */ +static void uas_unlink_data_urbs(struct uas_dev_info *devinfo, +				 struct uas_cmd_info *cmdinfo, +				 unsigned long *lock_flags) +{ +	/* +	 * The UNLINK_DATA_URBS flag makes sure uas_try_complete +	 * (called by urb completion) doesn't release cmdinfo +	 * underneath us. +	 */ +	cmdinfo->state |= UNLINK_DATA_URBS; +	spin_unlock_irqrestore(&devinfo->lock, *lock_flags); -static DEFINE_SPINLOCK(uas_work_lock); -static LIST_HEAD(uas_work_list); +	if (cmdinfo->data_in_urb) +		usb_unlink_urb(cmdinfo->data_in_urb); +	if (cmdinfo->data_out_urb) +		usb_unlink_urb(cmdinfo->data_out_urb); + +	spin_lock_irqsave(&devinfo->lock, *lock_flags); +	cmdinfo->state &= ~UNLINK_DATA_URBS; +}  static void uas_do_work(struct work_struct *work)  { +	struct uas_dev_info *devinfo = +		container_of(work, struct uas_dev_info, work);  	struct uas_cmd_info *cmdinfo; -	struct list_head list; - -	spin_lock_irq(&uas_work_lock); -	list_replace_init(&uas_work_list, &list); -	spin_unlock_irq(&uas_work_lock); +	unsigned long flags; +	int err; -	list_for_each_entry(cmdinfo, &list, list) { +	spin_lock_irqsave(&devinfo->lock, flags); +	list_for_each_entry(cmdinfo, &devinfo->inflight_list, list) {  		struct scsi_pointer *scp = (void *)cmdinfo; -		struct scsi_cmnd *cmnd = container_of(scp, -							struct scsi_cmnd, SCp); -		uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_KERNEL); +		struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, +						      SCp); + +		if (!(cmdinfo->state & IS_IN_WORK_LIST)) +			continue; + +		err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); +		if (!err) +			cmdinfo->state &= ~IS_IN_WORK_LIST; +		else +			schedule_work(&devinfo->work);  	} +	spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_mark_cmd_dead(struct uas_dev_info *devinfo, +			      struct uas_cmd_info *cmdinfo, +			      int result, const char *caller) +{ +	struct scsi_pointer *scp = (void *)cmdinfo; +	struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); + +	uas_log_cmd_state(cmnd, caller); +	WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); +	WARN_ON_ONCE(cmdinfo->state & COMMAND_ABORTED); +	cmdinfo->state |= COMMAND_ABORTED; +	cmdinfo->state &= ~IS_IN_WORK_LIST; +	cmnd->result = result << 16; +	list_move_tail(&cmdinfo->list, &devinfo->dead_list);  } -static DECLARE_WORK(uas_work, uas_do_work); +static void uas_abort_inflight(struct uas_dev_info *devinfo, int result, +			       const char *caller) +{ +	struct uas_cmd_info *cmdinfo; +	struct uas_cmd_info *temp; +	unsigned long flags; + +	spin_lock_irqsave(&devinfo->lock, flags); +	list_for_each_entry_safe(cmdinfo, temp, &devinfo->inflight_list, list) +		uas_mark_cmd_dead(devinfo, cmdinfo, result, caller); +	spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_add_work(struct uas_cmd_info *cmdinfo) +{ +	struct scsi_pointer *scp = (void *)cmdinfo; +	struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, SCp); +	struct uas_dev_info *devinfo = cmnd->device->hostdata; + +	WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); +	cmdinfo->state |= IS_IN_WORK_LIST; +	schedule_work(&devinfo->work); +} + +static void uas_zap_dead(struct uas_dev_info *devinfo) +{ +	struct uas_cmd_info *cmdinfo; +	struct uas_cmd_info *temp; +	unsigned long flags; + +	spin_lock_irqsave(&devinfo->lock, flags); +	list_for_each_entry_safe(cmdinfo, temp, &devinfo->dead_list, list) { +		struct scsi_pointer *scp = (void *)cmdinfo; +		struct scsi_cmnd *cmnd = container_of(scp, struct scsi_cmnd, +						      SCp); +		uas_log_cmd_state(cmnd, __func__); +		WARN_ON_ONCE(!(cmdinfo->state & COMMAND_ABORTED)); +		/* all urbs are killed, clear inflight bits */ +		cmdinfo->state &= ~(COMMAND_INFLIGHT | +				    DATA_IN_URB_INFLIGHT | +				    DATA_OUT_URB_INFLIGHT); +		uas_try_complete(cmnd, __func__); +	} +	devinfo->running_task = 0; +	spin_unlock_irqrestore(&devinfo->lock, flags); +}  static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd)  { @@ -165,10 +230,6 @@ static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd)  	}  	cmnd->result = sense_iu->status; -	if (sdev->current_cmnd) -		sdev->current_cmnd = NULL; -	cmnd->scsi_done(cmnd); -	usb_free_urb(urb);  }  static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd) @@ -192,92 +253,216 @@ static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd)  	}  	cmnd->result = sense_iu->status; -	if (sdev->current_cmnd) -		sdev->current_cmnd = NULL; +} + +static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *caller) +{ +	struct uas_cmd_info *ci = (void *)&cmnd->SCp; + +	scmd_printk(KERN_INFO, cmnd, "%s %p tag %d, inflight:" +		    "%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", +		    caller, cmnd, cmnd->request->tag, +		    (ci->state & SUBMIT_STATUS_URB)     ? " s-st"  : "", +		    (ci->state & ALLOC_DATA_IN_URB)     ? " a-in"  : "", +		    (ci->state & SUBMIT_DATA_IN_URB)    ? " s-in"  : "", +		    (ci->state & ALLOC_DATA_OUT_URB)    ? " a-out" : "", +		    (ci->state & SUBMIT_DATA_OUT_URB)   ? " s-out" : "", +		    (ci->state & ALLOC_CMD_URB)         ? " a-cmd" : "", +		    (ci->state & SUBMIT_CMD_URB)        ? " s-cmd" : "", +		    (ci->state & COMMAND_INFLIGHT)      ? " CMD"   : "", +		    (ci->state & DATA_IN_URB_INFLIGHT)  ? " IN"    : "", +		    (ci->state & DATA_OUT_URB_INFLIGHT) ? " OUT"   : "", +		    (ci->state & COMMAND_COMPLETED)     ? " done"  : "", +		    (ci->state & COMMAND_ABORTED)       ? " abort" : "", +		    (ci->state & UNLINK_DATA_URBS)      ? " unlink": "", +		    (ci->state & IS_IN_WORK_LIST)       ? " work"  : ""); +} + +static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller) +{ +	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; + +	WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); +	if (cmdinfo->state & (COMMAND_INFLIGHT | +			      DATA_IN_URB_INFLIGHT | +			      DATA_OUT_URB_INFLIGHT | +			      UNLINK_DATA_URBS)) +		return -EBUSY; +	WARN_ON_ONCE(cmdinfo->state & COMMAND_COMPLETED); +	cmdinfo->state |= COMMAND_COMPLETED; +	usb_free_urb(cmdinfo->data_in_urb); +	usb_free_urb(cmdinfo->data_out_urb); +	if (cmdinfo->state & COMMAND_ABORTED) +		scmd_printk(KERN_INFO, cmnd, "abort completed\n"); +	list_del(&cmdinfo->list);  	cmnd->scsi_done(cmnd); -	usb_free_urb(urb); +	return 0;  }  static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, -							unsigned direction) +			  unsigned direction)  {  	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp;  	int err; -	cmdinfo->state = direction | SUBMIT_SENSE_URB; +	cmdinfo->state |= direction | SUBMIT_STATUS_URB;  	err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC);  	if (err) { -		spin_lock(&uas_work_lock); -		list_add_tail(&cmdinfo->list, &uas_work_list); -		spin_unlock(&uas_work_lock); -		schedule_work(&uas_work); +		uas_add_work(cmdinfo);  	}  }  static void uas_stat_cmplt(struct urb *urb)  {  	struct iu *iu = urb->transfer_buffer; -	struct scsi_device *sdev = urb->context; -	struct uas_dev_info *devinfo = sdev->hostdata; +	struct Scsi_Host *shost = urb->context; +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata;  	struct scsi_cmnd *cmnd; +	struct uas_cmd_info *cmdinfo; +	unsigned long flags;  	u16 tag;  	if (urb->status) { -		dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); +		if (urb->status == -ENOENT) { +			dev_err(&urb->dev->dev, "stat urb: killed, stream %d\n", +				urb->stream_id); +		} else { +			dev_err(&urb->dev->dev, "stat urb: status %d\n", +				urb->status); +		}  		usb_free_urb(urb);  		return;  	} +	if (devinfo->resetting) { +		usb_free_urb(urb); +		return; +	} + +	spin_lock_irqsave(&devinfo->lock, flags);  	tag = be16_to_cpup(&iu->tag) - 1; -	if (sdev->current_cmnd) -		cmnd = sdev->current_cmnd; +	if (tag == 0) +		cmnd = devinfo->cmnd;  	else -		cmnd = scsi_find_tag(sdev, tag); -	if (!cmnd) +		cmnd = scsi_host_find_tag(shost, tag - 1); + +	if (!cmnd) { +		if (iu->iu_id == IU_ID_RESPONSE) { +			if (!devinfo->running_task) +				dev_warn(&urb->dev->dev, +				    "stat urb: recv unexpected response iu\n"); +			/* store results for uas_eh_task_mgmt() */ +			memcpy(&devinfo->response, iu, sizeof(devinfo->response)); +		} +		usb_free_urb(urb); +		spin_unlock_irqrestore(&devinfo->lock, flags);  		return; +	} +	cmdinfo = (void *)&cmnd->SCp;  	switch (iu->iu_id) {  	case IU_ID_STATUS: +		if (devinfo->cmnd == cmnd) +			devinfo->cmnd = NULL; +  		if (urb->actual_length < 16)  			devinfo->uas_sense_old = 1;  		if (devinfo->uas_sense_old)  			uas_sense_old(urb, cmnd);  		else  			uas_sense(urb, cmnd); +		if (cmnd->result != 0) { +			/* cancel data transfers on error */ +			uas_unlink_data_urbs(devinfo, cmdinfo, &flags); +		} +		cmdinfo->state &= ~COMMAND_INFLIGHT; +		uas_try_complete(cmnd, __func__);  		break;  	case IU_ID_READ_READY: +		if (!cmdinfo->data_in_urb || +				(cmdinfo->state & DATA_IN_URB_INFLIGHT)) { +			scmd_printk(KERN_ERR, cmnd, "unexpected read rdy\n"); +			break; +		}  		uas_xfer_data(urb, cmnd, SUBMIT_DATA_IN_URB);  		break;  	case IU_ID_WRITE_READY: +		if (!cmdinfo->data_out_urb || +				(cmdinfo->state & DATA_OUT_URB_INFLIGHT)) { +			scmd_printk(KERN_ERR, cmnd, "unexpected write rdy\n"); +			break; +		}  		uas_xfer_data(urb, cmnd, SUBMIT_DATA_OUT_URB);  		break;  	default:  		scmd_printk(KERN_ERR, cmnd,  			"Bogus IU (%d) received on status pipe\n", iu->iu_id);  	} +	usb_free_urb(urb); +	spin_unlock_irqrestore(&devinfo->lock, flags);  }  static void uas_data_cmplt(struct urb *urb)  { -	struct scsi_data_buffer *sdb = urb->context; -	sdb->resid = sdb->length - urb->actual_length; +	struct scsi_cmnd *cmnd = urb->context; +	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; +	struct scsi_data_buffer *sdb = NULL; +	unsigned long flags; + +	spin_lock_irqsave(&devinfo->lock, flags); +	if (cmdinfo->data_in_urb == urb) { +		sdb = scsi_in(cmnd); +		cmdinfo->state &= ~DATA_IN_URB_INFLIGHT; +	} else if (cmdinfo->data_out_urb == urb) { +		sdb = scsi_out(cmnd); +		cmdinfo->state &= ~DATA_OUT_URB_INFLIGHT; +	} +	if (sdb == NULL) { +		WARN_ON_ONCE(1); +	} else if (urb->status) { +		if (urb->status != -ECONNRESET) { +			uas_log_cmd_state(cmnd, __func__); +			scmd_printk(KERN_ERR, cmnd, +				"data cmplt err %d stream %d\n", +				urb->status, urb->stream_id); +		} +		/* error: no data transfered */ +		sdb->resid = sdb->length; +	} else { +		sdb->resid = sdb->length - urb->actual_length; +	} +	uas_try_complete(cmnd, __func__); +	spin_unlock_irqrestore(&devinfo->lock, flags); +} + +static void uas_cmd_cmplt(struct urb *urb) +{ +	struct scsi_cmnd *cmnd = urb->context; + +	if (urb->status) { +		uas_log_cmd_state(cmnd, __func__); +		scmd_printk(KERN_ERR, cmnd, "cmd cmplt err %d\n", urb->status); +	}  	usb_free_urb(urb);  }  static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, -				unsigned int pipe, u16 stream_id, -				struct scsi_data_buffer *sdb, -				enum dma_data_direction dir) +				      unsigned int pipe, u16 stream_id, +				      struct scsi_cmnd *cmnd, +				      enum dma_data_direction dir)  {  	struct usb_device *udev = devinfo->udev;  	struct urb *urb = usb_alloc_urb(0, gfp); +	struct scsi_data_buffer *sdb = (dir == DMA_FROM_DEVICE) +		? scsi_in(cmnd) : scsi_out(cmnd);  	if (!urb)  		goto out; -	usb_fill_bulk_urb(urb, udev, pipe, NULL, sdb->length, uas_data_cmplt, -									sdb); -	if (devinfo->use_streams) -		urb->stream_id = stream_id; +	usb_fill_bulk_urb(urb, udev, pipe, NULL, sdb->length, +			  uas_data_cmplt, cmnd); +	urb->stream_id = stream_id;  	urb->num_sgs = udev->bus->sg_tablesize ? sdb->table.nents : 0;  	urb->sg = sdb->table.sgl;   out: @@ -285,7 +470,7 @@ static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp,  }  static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, -					struct scsi_cmnd *cmnd, u16 stream_id) +				       struct Scsi_Host *shost, u16 stream_id)  {  	struct usb_device *udev = devinfo->udev;  	struct urb *urb = usb_alloc_urb(0, gfp); @@ -294,12 +479,12 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp,  	if (!urb)  		goto out; -	iu = kmalloc(sizeof(*iu), gfp); +	iu = kzalloc(sizeof(*iu), gfp);  	if (!iu)  		goto free;  	usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), -						uas_stat_cmplt, cmnd->device); +						uas_stat_cmplt, shost);  	urb->stream_id = stream_id;  	urb->transfer_flags |= URB_FREE_BUFFER;   out: @@ -310,7 +495,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp,  }  static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, -					struct scsi_cmnd *cmnd, u16 stream_id) +					struct scsi_cmnd *cmnd)  {  	struct usb_device *udev = devinfo->udev;  	struct scsi_device *sdev = cmnd->device; @@ -325,19 +510,22 @@ static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp,  	if (len < 0)  		len = 0;  	len = ALIGN(len, 4); -	iu = kmalloc(sizeof(*iu) + len, gfp); +	iu = kzalloc(sizeof(*iu) + len, gfp);  	if (!iu)  		goto free;  	iu->iu_id = IU_ID_COMMAND; -	iu->tag = cpu_to_be16(stream_id); +	if (blk_rq_tagged(cmnd->request)) +		iu->tag = cpu_to_be16(cmnd->request->tag + 2); +	else +		iu->tag = cpu_to_be16(1);  	iu->prio_attr = UAS_SIMPLE_TAG;  	iu->len = len;  	int_to_scsilun(sdev->lun, &iu->lun);  	memcpy(iu->cdb, cmnd->cmnd, cmnd->cmd_len);  	usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu) + len, -							usb_free_urb, NULL); +							uas_cmd_cmplt, cmnd);  	urb->transfer_flags |= URB_FREE_BUFFER;   out:  	return urb; @@ -346,113 +534,212 @@ static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp,  	return NULL;  } +static int uas_submit_task_urb(struct scsi_cmnd *cmnd, gfp_t gfp, +			       u8 function, u16 stream_id) +{ +	struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; +	struct usb_device *udev = devinfo->udev; +	struct urb *urb = usb_alloc_urb(0, gfp); +	struct task_mgmt_iu *iu; +	int err = -ENOMEM; + +	if (!urb) +		goto err; + +	iu = kzalloc(sizeof(*iu), gfp); +	if (!iu) +		goto err; + +	iu->iu_id = IU_ID_TASK_MGMT; +	iu->tag = cpu_to_be16(stream_id); +	int_to_scsilun(cmnd->device->lun, &iu->lun); + +	iu->function = function; +	switch (function) { +	case TMF_ABORT_TASK: +		if (blk_rq_tagged(cmnd->request)) +			iu->task_tag = cpu_to_be16(cmnd->request->tag + 2); +		else +			iu->task_tag = cpu_to_be16(1); +		break; +	} + +	usb_fill_bulk_urb(urb, udev, devinfo->cmd_pipe, iu, sizeof(*iu), +			  uas_cmd_cmplt, cmnd); +	urb->transfer_flags |= URB_FREE_BUFFER; + +	usb_anchor_urb(urb, &devinfo->cmd_urbs); +	err = usb_submit_urb(urb, gfp); +	if (err) { +		usb_unanchor_urb(urb); +		uas_log_cmd_state(cmnd, __func__); +		scmd_printk(KERN_ERR, cmnd, "task submission err %d\n", err); +		goto err; +	} + +	return 0; + +err: +	usb_free_urb(urb); +	return err; +} +  /*   * Why should I request the Status IU before sending the Command IU?  Spec   * says to, but also says the device may receive them in any order.  Seems   * daft to me.   */ -static int uas_submit_urbs(struct scsi_cmnd *cmnd, -					struct uas_dev_info *devinfo, gfp_t gfp) +static struct urb *uas_submit_sense_urb(struct scsi_cmnd *cmnd, +					gfp_t gfp, unsigned int stream)  { -	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	struct Scsi_Host *shost = cmnd->device->host; +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; +	struct urb *urb; +	int err; -	if (cmdinfo->state & ALLOC_SENSE_URB) { -		cmdinfo->sense_urb = uas_alloc_sense_urb(devinfo, gfp, cmnd, -							cmdinfo->stream); -		if (!cmdinfo->sense_urb) -			return SCSI_MLQUEUE_DEVICE_BUSY; -		cmdinfo->state &= ~ALLOC_SENSE_URB; +	urb = uas_alloc_sense_urb(devinfo, gfp, shost, stream); +	if (!urb) +		return NULL; +	usb_anchor_urb(urb, &devinfo->sense_urbs); +	err = usb_submit_urb(urb, gfp); +	if (err) { +		usb_unanchor_urb(urb); +		uas_log_cmd_state(cmnd, __func__); +		shost_printk(KERN_INFO, shost, +			     "sense urb submission error %d stream %d\n", +			     err, stream); +		usb_free_urb(urb); +		return NULL;  	} +	return urb; +} -	if (cmdinfo->state & SUBMIT_SENSE_URB) { -		if (usb_submit_urb(cmdinfo->sense_urb, gfp)) { -			scmd_printk(KERN_INFO, cmnd, -					"sense urb submission failure\n"); +static int uas_submit_urbs(struct scsi_cmnd *cmnd, +			   struct uas_dev_info *devinfo, gfp_t gfp) +{ +	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	struct urb *urb; +	int err; + +	WARN_ON_ONCE(!spin_is_locked(&devinfo->lock)); +	if (cmdinfo->state & SUBMIT_STATUS_URB) { +		urb = uas_submit_sense_urb(cmnd, gfp, cmdinfo->stream); +		if (!urb)  			return SCSI_MLQUEUE_DEVICE_BUSY; -		} -		cmdinfo->state &= ~SUBMIT_SENSE_URB; +		cmdinfo->state &= ~SUBMIT_STATUS_URB;  	}  	if (cmdinfo->state & ALLOC_DATA_IN_URB) {  		cmdinfo->data_in_urb = uas_alloc_data_urb(devinfo, gfp,  					devinfo->data_in_pipe, cmdinfo->stream, -					scsi_in(cmnd), DMA_FROM_DEVICE); +					cmnd, DMA_FROM_DEVICE);  		if (!cmdinfo->data_in_urb)  			return SCSI_MLQUEUE_DEVICE_BUSY;  		cmdinfo->state &= ~ALLOC_DATA_IN_URB;  	}  	if (cmdinfo->state & SUBMIT_DATA_IN_URB) { -		if (usb_submit_urb(cmdinfo->data_in_urb, gfp)) { +		usb_anchor_urb(cmdinfo->data_in_urb, &devinfo->data_urbs); +		err = usb_submit_urb(cmdinfo->data_in_urb, gfp); +		if (err) { +			usb_unanchor_urb(cmdinfo->data_in_urb); +			uas_log_cmd_state(cmnd, __func__);  			scmd_printk(KERN_INFO, cmnd, -					"data in urb submission failure\n"); +				"data in urb submission error %d stream %d\n", +				err, cmdinfo->data_in_urb->stream_id);  			return SCSI_MLQUEUE_DEVICE_BUSY;  		}  		cmdinfo->state &= ~SUBMIT_DATA_IN_URB; +		cmdinfo->state |= DATA_IN_URB_INFLIGHT;  	}  	if (cmdinfo->state & ALLOC_DATA_OUT_URB) {  		cmdinfo->data_out_urb = uas_alloc_data_urb(devinfo, gfp,  					devinfo->data_out_pipe, cmdinfo->stream, -					scsi_out(cmnd), DMA_TO_DEVICE); +					cmnd, DMA_TO_DEVICE);  		if (!cmdinfo->data_out_urb)  			return SCSI_MLQUEUE_DEVICE_BUSY;  		cmdinfo->state &= ~ALLOC_DATA_OUT_URB;  	}  	if (cmdinfo->state & SUBMIT_DATA_OUT_URB) { -		if (usb_submit_urb(cmdinfo->data_out_urb, gfp)) { +		usb_anchor_urb(cmdinfo->data_out_urb, &devinfo->data_urbs); +		err = usb_submit_urb(cmdinfo->data_out_urb, gfp); +		if (err) { +			usb_unanchor_urb(cmdinfo->data_out_urb); +			uas_log_cmd_state(cmnd, __func__);  			scmd_printk(KERN_INFO, cmnd, -					"data out urb submission failure\n"); +				"data out urb submission error %d stream %d\n", +				err, cmdinfo->data_out_urb->stream_id);  			return SCSI_MLQUEUE_DEVICE_BUSY;  		}  		cmdinfo->state &= ~SUBMIT_DATA_OUT_URB; +		cmdinfo->state |= DATA_OUT_URB_INFLIGHT;  	}  	if (cmdinfo->state & ALLOC_CMD_URB) { -		cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd, -							cmdinfo->stream); +		cmdinfo->cmd_urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd);  		if (!cmdinfo->cmd_urb)  			return SCSI_MLQUEUE_DEVICE_BUSY;  		cmdinfo->state &= ~ALLOC_CMD_URB;  	}  	if (cmdinfo->state & SUBMIT_CMD_URB) { -		if (usb_submit_urb(cmdinfo->cmd_urb, gfp)) { +		usb_anchor_urb(cmdinfo->cmd_urb, &devinfo->cmd_urbs); +		err = usb_submit_urb(cmdinfo->cmd_urb, gfp); +		if (err) { +			usb_unanchor_urb(cmdinfo->cmd_urb); +			uas_log_cmd_state(cmnd, __func__);  			scmd_printk(KERN_INFO, cmnd, -					"cmd urb submission failure\n"); +				    "cmd urb submission error %d\n", err);  			return SCSI_MLQUEUE_DEVICE_BUSY;  		} +		cmdinfo->cmd_urb = NULL;  		cmdinfo->state &= ~SUBMIT_CMD_URB; +		cmdinfo->state |= COMMAND_INFLIGHT;  	}  	return 0;  } -static int uas_queuecommand(struct scsi_cmnd *cmnd, +static int uas_queuecommand_lck(struct scsi_cmnd *cmnd,  					void (*done)(struct scsi_cmnd *))  {  	struct scsi_device *sdev = cmnd->device;  	struct uas_dev_info *devinfo = sdev->hostdata;  	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	unsigned long flags;  	int err;  	BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); -	if (!cmdinfo->sense_urb && sdev->current_cmnd) +	spin_lock_irqsave(&devinfo->lock, flags); + +	if (devinfo->resetting) { +		cmnd->result = DID_ERROR << 16; +		cmnd->scsi_done(cmnd); +		spin_unlock_irqrestore(&devinfo->lock, flags); +		return 0; +	} + +	if (devinfo->cmnd) { +		spin_unlock_irqrestore(&devinfo->lock, flags);  		return SCSI_MLQUEUE_DEVICE_BUSY; +	} + +	memset(cmdinfo, 0, sizeof(*cmdinfo));  	if (blk_rq_tagged(cmnd->request)) { -		cmdinfo->stream = cmnd->request->tag + 1; +		cmdinfo->stream = cmnd->request->tag + 2;  	} else { -		sdev->current_cmnd = cmnd; +		devinfo->cmnd = cmnd;  		cmdinfo->stream = 1;  	}  	cmnd->scsi_done = done; -	cmdinfo->state = ALLOC_SENSE_URB | SUBMIT_SENSE_URB | +	cmdinfo->state = SUBMIT_STATUS_URB |  			ALLOC_CMD_URB | SUBMIT_CMD_URB;  	switch (cmnd->sc_data_direction) { @@ -475,49 +762,130 @@ static int uas_queuecommand(struct scsi_cmnd *cmnd,  	err = uas_submit_urbs(cmnd, devinfo, GFP_ATOMIC);  	if (err) {  		/* If we did nothing, give up now */ -		if (cmdinfo->state & SUBMIT_SENSE_URB) { -			usb_free_urb(cmdinfo->sense_urb); +		if (cmdinfo->state & SUBMIT_STATUS_URB) { +			spin_unlock_irqrestore(&devinfo->lock, flags);  			return SCSI_MLQUEUE_DEVICE_BUSY;  		} -		spin_lock(&uas_work_lock); -		list_add_tail(&cmdinfo->list, &uas_work_list); -		spin_unlock(&uas_work_lock); -		schedule_work(&uas_work); +		uas_add_work(cmdinfo);  	} +	list_add_tail(&cmdinfo->list, &devinfo->inflight_list); +	spin_unlock_irqrestore(&devinfo->lock, flags);  	return 0;  } -static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) +static DEF_SCSI_QCMD(uas_queuecommand) + +static int uas_eh_task_mgmt(struct scsi_cmnd *cmnd, +			    const char *fname, u8 function)  { -	struct scsi_device *sdev = cmnd->device; -	sdev_printk(KERN_INFO, sdev, "%s tag %d\n", __func__, -							cmnd->request->tag); +	struct Scsi_Host *shost = cmnd->device->host; +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; +	u16 tag = devinfo->qdepth; +	unsigned long flags; +	struct urb *sense_urb; +	int result = SUCCESS; + +	spin_lock_irqsave(&devinfo->lock, flags); + +	if (devinfo->resetting) { +		spin_unlock_irqrestore(&devinfo->lock, flags); +		return FAILED; +	} + +	if (devinfo->running_task) { +		shost_printk(KERN_INFO, shost, +			     "%s: %s: error already running a task\n", +			     __func__, fname); +		spin_unlock_irqrestore(&devinfo->lock, flags); +		return FAILED; +	} + +	devinfo->running_task = 1; +	memset(&devinfo->response, 0, sizeof(devinfo->response)); +	sense_urb = uas_submit_sense_urb(cmnd, GFP_ATOMIC, +					 devinfo->use_streams ? tag : 0); +	if (!sense_urb) { +		shost_printk(KERN_INFO, shost, +			     "%s: %s: submit sense urb failed\n", +			     __func__, fname); +		devinfo->running_task = 0; +		spin_unlock_irqrestore(&devinfo->lock, flags); +		return FAILED; +	} +	if (uas_submit_task_urb(cmnd, GFP_ATOMIC, function, tag)) { +		shost_printk(KERN_INFO, shost, +			     "%s: %s: submit task mgmt urb failed\n", +			     __func__, fname); +		devinfo->running_task = 0; +		spin_unlock_irqrestore(&devinfo->lock, flags); +		usb_kill_urb(sense_urb); +		return FAILED; +	} +	spin_unlock_irqrestore(&devinfo->lock, flags); + +	if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 3000) == 0) { +		/* +		 * Note we deliberately do not clear running_task here. If we +		 * allow new tasks to be submitted, there is no way to figure +		 * out if a received response_iu is for the failed task or for +		 * the new one. A bus-reset will eventually clear running_task. +		 */ +		shost_printk(KERN_INFO, shost, +			     "%s: %s timed out\n", __func__, fname); +		return FAILED; +	} + +	spin_lock_irqsave(&devinfo->lock, flags); +	devinfo->running_task = 0; +	if (be16_to_cpu(devinfo->response.tag) != tag) { +		shost_printk(KERN_INFO, shost, +			     "%s: %s failed (wrong tag %d/%d)\n", __func__, +			     fname, be16_to_cpu(devinfo->response.tag), tag); +		result = FAILED; +	} else if (devinfo->response.response_code != RC_TMF_COMPLETE) { +		shost_printk(KERN_INFO, shost, +			     "%s: %s failed (rc 0x%x)\n", __func__, +			     fname, devinfo->response.response_code); +		result = FAILED; +	} +	spin_unlock_irqrestore(&devinfo->lock, flags); -/* XXX: Send ABORT TASK Task Management command */ -	return FAILED; +	return result;  } -static int uas_eh_device_reset_handler(struct scsi_cmnd *cmnd) +static int uas_eh_abort_handler(struct scsi_cmnd *cmnd)  { -	struct scsi_device *sdev = cmnd->device; -	sdev_printk(KERN_INFO, sdev, "%s tag %d\n", __func__, -							cmnd->request->tag); +	struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; +	struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata; +	unsigned long flags; +	int ret; + +	spin_lock_irqsave(&devinfo->lock, flags); + +	if (devinfo->resetting) { +		spin_unlock_irqrestore(&devinfo->lock, flags); +		return FAILED; +	} -/* XXX: Send LOGICAL UNIT RESET Task Management command */ -	return FAILED; +	uas_mark_cmd_dead(devinfo, cmdinfo, DID_ABORT, __func__); +	if (cmdinfo->state & COMMAND_INFLIGHT) { +		spin_unlock_irqrestore(&devinfo->lock, flags); +		ret = uas_eh_task_mgmt(cmnd, "ABORT TASK", TMF_ABORT_TASK); +	} else { +		uas_unlink_data_urbs(devinfo, cmdinfo, &flags); +		uas_try_complete(cmnd, __func__); +		spin_unlock_irqrestore(&devinfo->lock, flags); +		ret = SUCCESS; +	} +	return ret;  } -static int uas_eh_target_reset_handler(struct scsi_cmnd *cmnd) +static int uas_eh_device_reset_handler(struct scsi_cmnd *cmnd)  { -	struct scsi_device *sdev = cmnd->device; -	sdev_printk(KERN_INFO, sdev, "%s tag %d\n", __func__, -							cmnd->request->tag); - -/* XXX: Can we reset just the one USB interface? - * Would calling usb_set_interface() have the right effect? - */ -	return FAILED; +	sdev_printk(KERN_INFO, cmnd->device, "%s\n", __func__); +	return uas_eh_task_mgmt(cmnd, "LOGICAL UNIT RESET", +				TMF_LOGICAL_UNIT_RESET);  }  static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd) @@ -525,19 +893,57 @@ static int uas_eh_bus_reset_handler(struct scsi_cmnd *cmnd)  	struct scsi_device *sdev = cmnd->device;  	struct uas_dev_info *devinfo = sdev->hostdata;  	struct usb_device *udev = devinfo->udev; +	int err; + +	err = usb_lock_device_for_reset(udev, devinfo->intf); +	if (err) { +		shost_printk(KERN_ERR, sdev->host, +			     "%s FAILED to get lock err %d\n", __func__, err); +		return FAILED; +	} + +	shost_printk(KERN_INFO, sdev->host, "%s start\n", __func__); +	devinfo->resetting = 1; +	uas_abort_inflight(devinfo, DID_RESET, __func__); +	usb_kill_anchored_urbs(&devinfo->cmd_urbs); +	usb_kill_anchored_urbs(&devinfo->sense_urbs); +	usb_kill_anchored_urbs(&devinfo->data_urbs); +	uas_zap_dead(devinfo); +	err = usb_reset_device(udev); +	devinfo->resetting = 0; -	sdev_printk(KERN_INFO, sdev, "%s tag %d\n", __func__, -							cmnd->request->tag); +	usb_unlock_device(udev); -	if (usb_reset_device(udev)) -		return SUCCESS; +	if (err) { +		shost_printk(KERN_INFO, sdev->host, "%s FAILED\n", __func__); +		return FAILED; +	} -	return FAILED; +	shost_printk(KERN_INFO, sdev->host, "%s success\n", __func__); +	return SUCCESS;  }  static int uas_slave_alloc(struct scsi_device *sdev)  { -	sdev->hostdata = (void *)sdev->host->hostdata[0]; +	sdev->hostdata = (void *)sdev->host->hostdata; + +	/* USB has unusual DMA-alignment requirements: Although the +	 * starting address of each scatter-gather element doesn't matter, +	 * the length of each element except the last must be divisible +	 * by the Bulk maxpacket value.  There's currently no way to +	 * express this by block-layer constraints, so we'll cop out +	 * and simply require addresses to be aligned at 512-byte +	 * boundaries.  This is okay since most block I/O involves +	 * hardware sectors that are multiples of 512 bytes in length, +	 * and since host controllers up through USB 2.0 have maxpacket +	 * values no larger than 512. +	 * +	 * But it doesn't suffice for Wireless USB, where Bulk maxpacket +	 * values can be as large as 2048.  To make that work properly +	 * will require changes to the block layer. +	 */ +	blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); +  	return 0;  } @@ -545,7 +951,7 @@ static int uas_slave_configure(struct scsi_device *sdev)  {  	struct uas_dev_info *devinfo = sdev->hostdata;  	scsi_set_tag_type(sdev, MSG_ORDERED_TAG); -	scsi_activate_tcq(sdev, devinfo->qdepth - 1); +	scsi_activate_tcq(sdev, devinfo->qdepth - 2);  	return 0;  } @@ -557,7 +963,6 @@ static struct scsi_host_template uas_host_template = {  	.slave_configure = uas_slave_configure,  	.eh_abort_handler = uas_eh_abort_handler,  	.eh_device_reset_handler = uas_eh_device_reset_handler, -	.eh_target_reset_handler = uas_eh_target_reset_handler,  	.eh_bus_reset_handler = uas_eh_bus_reset_handler,  	.can_queue = 65536,	/* Is there a limit on the _host_ ? */  	.this_id = -1, @@ -567,7 +972,14 @@ static struct scsi_host_template uas_host_template = {  	.ordered_tag = 1,  }; +#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ +		    vendorName, productName, useProtocol, useTransport, \ +		    initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ +	.driver_info = (flags) } +  static struct usb_device_id uas_usb_ids[] = { +#	include "unusual_uas.h"  	{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) },  	{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_UAS) },  	/* 0xaa is a prototype device I happen to have access to */ @@ -576,114 +988,127 @@ static struct usb_device_id uas_usb_ids[] = {  };  MODULE_DEVICE_TABLE(usb, uas_usb_ids); -static void uas_configure_endpoints(struct uas_dev_info *devinfo) +#undef UNUSUAL_DEV + +static int uas_switch_interface(struct usb_device *udev, +				struct usb_interface *intf) +{ +	int alt; + +	alt = uas_find_uas_alt_setting(intf); +	if (alt < 0) +		return alt; + +	return usb_set_interface(udev, +			intf->altsetting[0].desc.bInterfaceNumber, alt); +} + +static int uas_configure_endpoints(struct uas_dev_info *devinfo)  {  	struct usb_host_endpoint *eps[4] = { }; -	struct usb_interface *intf = devinfo->intf;  	struct usb_device *udev = devinfo->udev; -	struct usb_host_endpoint *endpoint = intf->cur_altsetting->endpoint; -	unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; +	int r;  	devinfo->uas_sense_old = 0; - -	for (i = 0; i < n_endpoints; i++) { -		unsigned char *extra = endpoint[i].extra; -		int len = endpoint[i].extralen; -		while (len > 1) { -			if (extra[1] == USB_DT_PIPE_USAGE) { -				unsigned pipe_id = extra[2]; -				if (pipe_id > 0 && pipe_id < 5) -					eps[pipe_id - 1] = &endpoint[i]; -				break; -			} -			len -= extra[0]; -			extra += extra[0]; -		} -	} - -	/* -	 * Assume that if we didn't find a control pipe descriptor, we're -	 * using a device with old firmware that happens to be set up like -	 * this. -	 */ -	if (!eps[0]) { -		devinfo->cmd_pipe = usb_sndbulkpipe(udev, 1); -		devinfo->status_pipe = usb_rcvbulkpipe(udev, 1); -		devinfo->data_in_pipe = usb_rcvbulkpipe(udev, 2); -		devinfo->data_out_pipe = usb_sndbulkpipe(udev, 2); - -		eps[1] = usb_pipe_endpoint(udev, devinfo->status_pipe); -		eps[2] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); -		eps[3] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); -	} else { -		devinfo->cmd_pipe = usb_sndbulkpipe(udev, -						eps[0]->desc.bEndpointAddress); -		devinfo->status_pipe = usb_rcvbulkpipe(udev, -						eps[1]->desc.bEndpointAddress); -		devinfo->data_in_pipe = usb_rcvbulkpipe(udev, -						eps[2]->desc.bEndpointAddress); -		devinfo->data_out_pipe = usb_sndbulkpipe(udev, -						eps[3]->desc.bEndpointAddress); -	} - -	devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, 3, 256, -								GFP_KERNEL); -	if (devinfo->qdepth < 0) { +	devinfo->cmnd = NULL; + +	r = uas_find_endpoints(devinfo->intf->cur_altsetting, eps); +	if (r) +		return r; + +	devinfo->cmd_pipe = usb_sndbulkpipe(udev, +					    usb_endpoint_num(&eps[0]->desc)); +	devinfo->status_pipe = usb_rcvbulkpipe(udev, +					    usb_endpoint_num(&eps[1]->desc)); +	devinfo->data_in_pipe = usb_rcvbulkpipe(udev, +					    usb_endpoint_num(&eps[2]->desc)); +	devinfo->data_out_pipe = usb_sndbulkpipe(udev, +					    usb_endpoint_num(&eps[3]->desc)); + +	if (udev->speed != USB_SPEED_SUPER) {  		devinfo->qdepth = 256;  		devinfo->use_streams = 0;  	} else { +		devinfo->qdepth = usb_alloc_streams(devinfo->intf, eps + 1, +						    3, 256, GFP_NOIO); +		if (devinfo->qdepth < 0) +			return devinfo->qdepth;  		devinfo->use_streams = 1;  	} + +	return 0; +} + +static void uas_free_streams(struct uas_dev_info *devinfo) +{ +	struct usb_device *udev = devinfo->udev; +	struct usb_host_endpoint *eps[3]; + +	eps[0] = usb_pipe_endpoint(udev, devinfo->status_pipe); +	eps[1] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); +	eps[2] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); +	usb_free_streams(devinfo->intf, eps, 3, GFP_NOIO);  } -/* - * XXX: What I'd like to do here is register a SCSI host for each USB host in - * the system.  Follow usb-storage's design of registering a SCSI host for - * each USB device for the moment.  Can implement this by walking up the - * USB hierarchy until we find a USB host. - */  static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)  { -	int result; -	struct Scsi_Host *shost; +	int result = -ENOMEM; +	struct Scsi_Host *shost = NULL;  	struct uas_dev_info *devinfo;  	struct usb_device *udev = interface_to_usbdev(intf); -	if (id->bInterfaceProtocol == 0x50) { -		int ifnum = intf->cur_altsetting->desc.bInterfaceNumber; -/* XXX: Shouldn't assume that 1 is the alternative we want */ -		int ret = usb_set_interface(udev, ifnum, 1); -		if (ret) -			return -ENODEV; -	} +	if (!uas_use_uas_driver(intf, id)) +		return -ENODEV; -	devinfo = kmalloc(sizeof(struct uas_dev_info), GFP_KERNEL); -	if (!devinfo) -		return -ENOMEM; +	if (uas_switch_interface(udev, intf)) +		return -ENODEV; -	result = -ENOMEM; -	shost = scsi_host_alloc(&uas_host_template, sizeof(void *)); +	shost = scsi_host_alloc(&uas_host_template, +				sizeof(struct uas_dev_info));  	if (!shost) -		goto free; +		goto set_alt0;  	shost->max_cmd_len = 16 + 252;  	shost->max_id = 1; +	shost->max_lun = 256; +	shost->max_channel = 0;  	shost->sg_tablesize = udev->bus->sg_tablesize; -	result = scsi_add_host(shost, &intf->dev); -	if (result) -		goto free; -	shost->hostdata[0] = (unsigned long)devinfo; - +	devinfo = (struct uas_dev_info *)shost->hostdata;  	devinfo->intf = intf;  	devinfo->udev = udev; -	uas_configure_endpoints(devinfo); +	devinfo->resetting = 0; +	devinfo->running_task = 0; +	devinfo->shutdown = 0; +	init_usb_anchor(&devinfo->cmd_urbs); +	init_usb_anchor(&devinfo->sense_urbs); +	init_usb_anchor(&devinfo->data_urbs); +	spin_lock_init(&devinfo->lock); +	INIT_WORK(&devinfo->work, uas_do_work); +	INIT_LIST_HEAD(&devinfo->inflight_list); +	INIT_LIST_HEAD(&devinfo->dead_list); + +	result = uas_configure_endpoints(devinfo); +	if (result) +		goto set_alt0; + +	result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2); +	if (result) +		goto free_streams; -	scsi_scan_host(shost);  	usb_set_intfdata(intf, shost); +	result = scsi_add_host(shost, &intf->dev); +	if (result) +		goto free_streams; + +	scsi_scan_host(shost);  	return result; - free: -	kfree(devinfo); + +free_streams: +	uas_free_streams(devinfo); +	usb_set_intfdata(intf, NULL); +set_alt0: +	usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0);  	if (shost)  		scsi_host_put(shost);  	return result; @@ -691,58 +1116,146 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)  static int uas_pre_reset(struct usb_interface *intf)  { -/* XXX: Need to return 1 if it's not our device in error handling */ +	struct Scsi_Host *shost = usb_get_intfdata(intf); +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; +	unsigned long flags; + +	if (devinfo->shutdown) +		return 0; + +	/* Block new requests */ +	spin_lock_irqsave(shost->host_lock, flags); +	scsi_block_requests(shost); +	spin_unlock_irqrestore(shost->host_lock, flags); + +	/* Wait for any pending requests to complete */ +	flush_work(&devinfo->work); +	if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { +		shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); +		return 1; +	} + +	uas_free_streams(devinfo); +  	return 0;  }  static int uas_post_reset(struct usb_interface *intf)  { -/* XXX: Need to return 1 if it's not our device in error handling */ +	struct Scsi_Host *shost = usb_get_intfdata(intf); +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; +	unsigned long flags; + +	if (devinfo->shutdown) +		return 0; + +	if (uas_configure_endpoints(devinfo) != 0) { +		shost_printk(KERN_ERR, shost, +			     "%s: alloc streams error after reset", __func__); +		return 1; +	} + +	spin_lock_irqsave(shost->host_lock, flags); +	scsi_report_bus_reset(shost, 0); +	spin_unlock_irqrestore(shost->host_lock, flags); + +	scsi_unblock_requests(shost); +  	return 0;  } -static void uas_disconnect(struct usb_interface *intf) +static int uas_suspend(struct usb_interface *intf, pm_message_t message)  { -	struct usb_device *udev = interface_to_usbdev(intf); -	struct usb_host_endpoint *eps[3];  	struct Scsi_Host *shost = usb_get_intfdata(intf); -	struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; -	scsi_remove_host(shost); +	/* Wait for any pending requests to complete */ +	flush_work(&devinfo->work); +	if (usb_wait_anchor_empty_timeout(&devinfo->sense_urbs, 5000) == 0) { +		shost_printk(KERN_ERR, shost, "%s: timed out\n", __func__); +		return -ETIME; +	} -	eps[0] = usb_pipe_endpoint(udev, devinfo->status_pipe); -	eps[1] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); -	eps[2] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); -	usb_free_streams(intf, eps, 3, GFP_KERNEL); +	return 0; +} -	kfree(devinfo); +static int uas_resume(struct usb_interface *intf) +{ +	return 0; +} + +static int uas_reset_resume(struct usb_interface *intf) +{ +	struct Scsi_Host *shost = usb_get_intfdata(intf); +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; +	unsigned long flags; + +	if (uas_configure_endpoints(devinfo) != 0) { +		shost_printk(KERN_ERR, shost, +			     "%s: alloc streams error after reset", __func__); +		return -EIO; +	} + +	spin_lock_irqsave(shost->host_lock, flags); +	scsi_report_bus_reset(shost, 0); +	spin_unlock_irqrestore(shost->host_lock, flags); + +	return 0; +} + +static void uas_disconnect(struct usb_interface *intf) +{ +	struct Scsi_Host *shost = usb_get_intfdata(intf); +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + +	devinfo->resetting = 1; +	cancel_work_sync(&devinfo->work); +	uas_abort_inflight(devinfo, DID_NO_CONNECT, __func__); +	usb_kill_anchored_urbs(&devinfo->cmd_urbs); +	usb_kill_anchored_urbs(&devinfo->sense_urbs); +	usb_kill_anchored_urbs(&devinfo->data_urbs); +	uas_zap_dead(devinfo); +	scsi_remove_host(shost); +	uas_free_streams(devinfo); +	scsi_host_put(shost);  }  /* - * XXX: Should this plug into libusual so we can auto-upgrade devices from - * Bulk-Only to UAS? + * Put the device back in usb-storage mode on shutdown, as some BIOS-es + * hang on reboot when the device is still in uas mode. Note the reset is + * necessary as some devices won't revert to usb-storage mode without it.   */ +static void uas_shutdown(struct device *dev) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *udev = interface_to_usbdev(intf); +	struct Scsi_Host *shost = usb_get_intfdata(intf); +	struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata; + +	if (system_state != SYSTEM_RESTART) +		return; + +	devinfo->shutdown = 1; +	uas_free_streams(devinfo); +	usb_set_interface(udev, intf->altsetting[0].desc.bInterfaceNumber, 0); +	usb_reset_device(udev); +} +  static struct usb_driver uas_driver = {  	.name = "uas",  	.probe = uas_probe,  	.disconnect = uas_disconnect,  	.pre_reset = uas_pre_reset,  	.post_reset = uas_post_reset, +	.suspend = uas_suspend, +	.resume = uas_resume, +	.reset_resume = uas_reset_resume, +	.drvwrap.driver.shutdown = uas_shutdown,  	.id_table = uas_usb_ids,  }; -static int uas_init(void) -{ -	return usb_register(&uas_driver); -} - -static void uas_exit(void) -{ -	usb_deregister(&uas_driver); -} - -module_init(uas_init); -module_exit(uas_exit); +module_usb_driver(uas_driver);  MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Matthew Wilcox and Sarah Sharp"); +MODULE_AUTHOR( +	"Hans de Goede <hdegoede@redhat.com>, Matthew Wilcox and Sarah Sharp");  | 
