diff options
Diffstat (limited to 'drivers/usb/core/urb.c')
| -rw-r--r-- | drivers/usb/core/urb.c | 55 | 
1 files changed, 25 insertions, 30 deletions
diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 7c0555548ac..c14fc082864 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -137,6 +137,16 @@ void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor)  }  EXPORT_SYMBOL_GPL(usb_anchor_urb); +/* Callers must hold anchor->lock */ +static void __usb_unanchor_urb(struct urb *urb, struct usb_anchor *anchor) +{ +	urb->anchor = NULL; +	list_del(&urb->anchor_list); +	usb_put_urb(urb); +	if (list_empty(&anchor->urb_list)) +		wake_up(&anchor->wait); +} +  /**   * usb_unanchor_urb - unanchors an URB   * @urb: pointer to the urb to anchor @@ -156,17 +166,14 @@ void usb_unanchor_urb(struct urb *urb)  		return;  	spin_lock_irqsave(&anchor->lock, flags); -	if (unlikely(anchor != urb->anchor)) { -		/* we've lost the race to another thread */ -		spin_unlock_irqrestore(&anchor->lock, flags); -		return; -	} -	urb->anchor = NULL; -	list_del(&urb->anchor_list); +	/* +	 * At this point, we could be competing with another thread which +	 * has the same intention. To protect the urb from being unanchored +	 * twice, only the winner of the race gets the job. +	 */ +	if (likely(anchor == urb->anchor)) +		__usb_unanchor_urb(urb, anchor);  	spin_unlock_irqrestore(&anchor->lock, flags); -	usb_put_urb(urb); -	if (list_empty(&anchor->urb_list)) -		wake_up(&anchor->wait);  }  EXPORT_SYMBOL_GPL(usb_unanchor_urb); @@ -394,8 +401,11 @@ int usb_submit_urb(struct urb *urb, gfp_t mem_flags)  	};  	/* Check that the pipe's type matches the endpoint's type */ -	if (usb_pipetype(urb->pipe) != pipetypes[xfertype]) +	if (usb_pipetype(urb->pipe) != pipetypes[xfertype]) { +		dev_err(&dev->dev, "BOGUS urb xfer, pipe %x != type %x\n", +			usb_pipetype(urb->pipe), pipetypes[xfertype]);  		return -EPIPE;		/* The most suitable error code :-) */ +	}  	/* enforce simple/standard policy */  	allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT | URB_DIR_MASK | @@ -749,20 +759,11 @@ EXPORT_SYMBOL_GPL(usb_unpoison_anchored_urbs);  void usb_unlink_anchored_urbs(struct usb_anchor *anchor)  {  	struct urb *victim; -	unsigned long flags; -	spin_lock_irqsave(&anchor->lock, flags); -	while (!list_empty(&anchor->urb_list)) { -		victim = list_entry(anchor->urb_list.prev, struct urb, -				    anchor_list); -		usb_get_urb(victim); -		spin_unlock_irqrestore(&anchor->lock, flags); -		/* this will unanchor the URB */ +	while ((victim = usb_get_from_anchor(anchor)) != NULL) {  		usb_unlink_urb(victim);  		usb_put_urb(victim); -		spin_lock_irqsave(&anchor->lock, flags);  	} -	spin_unlock_irqrestore(&anchor->lock, flags);  }  EXPORT_SYMBOL_GPL(usb_unlink_anchored_urbs); @@ -799,12 +800,11 @@ struct urb *usb_get_from_anchor(struct usb_anchor *anchor)  		victim = list_entry(anchor->urb_list.next, struct urb,  				    anchor_list);  		usb_get_urb(victim); -		spin_unlock_irqrestore(&anchor->lock, flags); -		usb_unanchor_urb(victim); +		__usb_unanchor_urb(victim, anchor);  	} else { -		spin_unlock_irqrestore(&anchor->lock, flags);  		victim = NULL;  	} +	spin_unlock_irqrestore(&anchor->lock, flags);  	return victim;  } @@ -826,12 +826,7 @@ void usb_scuttle_anchored_urbs(struct usb_anchor *anchor)  	while (!list_empty(&anchor->urb_list)) {  		victim = list_entry(anchor->urb_list.prev, struct urb,  				    anchor_list); -		usb_get_urb(victim); -		spin_unlock_irqrestore(&anchor->lock, flags); -		/* this may free the URB */ -		usb_unanchor_urb(victim); -		usb_put_urb(victim); -		spin_lock_irqsave(&anchor->lock, flags); +		__usb_unanchor_urb(victim, anchor);  	}  	spin_unlock_irqrestore(&anchor->lock, flags);  }  | 
