diff options
author | Benoit Goby <benoit@android.com> | 2010-12-02 16:11:15 -0800 |
---|---|---|
committer | Benoit Goby <benoit@android.com> | 2010-12-03 14:02:44 -0800 |
commit | 3cf8cfddc7d1f4a2893f9a5d892b1214140c7c00 (patch) | |
tree | 55c978c467ced15cb482b4167a6c487055d02144 /drivers | |
parent | 1538e53fbca5ce8b466ab067ab9c46151f0e51bd (diff) |
usb: gadget: fsl_udc: Fix a race between ep_disable and ep_queue
Fixed a possible null pointer exception when an endpoint gets
disabled while a request is being enqueued in parallel.
Unmap the request buffer if we fail to enqueue the request.
Change-Id: If94cc278c2e6ab58adcf170511e676348365f3f9
Signed-off-by: Benoit Goby <benoit@android.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/gadget/fsl_udc_core.c | 56 |
1 files changed, 40 insertions, 16 deletions
diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c index 20fab22cc13b..2fab37a2a094 100644 --- a/drivers/usb/gadget/fsl_udc_core.c +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -846,9 +846,11 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) { struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); struct fsl_req *req = container_of(_req, struct fsl_req, req); - struct fsl_udc *udc; + struct fsl_udc *udc = ep->udc; unsigned long flags; + enum dma_data_direction dir; int is_iso = 0; + int status; /* catch various bogus parameters */ if (!_req || !req->req.complete || !req->req.buf @@ -856,17 +858,27 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) VDBG("%s, bad params", __func__); return -EINVAL; } - if (unlikely(!_ep || !ep->desc)) { + + spin_lock_irqsave(&udc->lock, flags); + + if (unlikely(!ep->desc)) { VDBG("%s, bad ep", __func__); + spin_unlock_irqrestore(&udc->lock, flags); return -EINVAL; } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { - if (req->req.length > ep->ep.maxpacket) + if (req->req.length > ep->ep.maxpacket) { + spin_unlock_irqrestore(&udc->lock, flags); return -EMSGSIZE; + } is_iso = 1; } - udc = ep->udc; + dir = ep_is_in(ep) ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + spin_unlock_irqrestore(&udc->lock, flags); + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) return -ESHUTDOWN; @@ -874,18 +886,12 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) /* map virtual address to hardware */ if (req->req.dma == DMA_ADDR_INVALID) { - req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, - req->req.buf, - req->req.length, ep_is_in(ep) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); + req->req.dma = dma_map_single(udc->gadget.dev.parent, + req->req.buf, req->req.length, dir); req->mapped = 1; } else { - dma_sync_single_for_device(ep->udc->gadget.dev.parent, - req->req.dma, req->req.length, - ep_is_in(ep) - ? DMA_TO_DEVICE - : DMA_FROM_DEVICE); + dma_sync_single_for_device(udc->gadget.dev.parent, + req->req.dma, req->req.length, dir); req->mapped = 0; } @@ -895,10 +901,19 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) /* build dtds and push them to device queue */ - if (fsl_req_to_dtd(req, gfp_flags)) - return -ENOMEM; + status = fsl_req_to_dtd(req, gfp_flags); + if (status) + goto err_unmap; spin_lock_irqsave(&udc->lock, flags); + + /* re-check if the ep has not been disabled */ + if (unlikely(!ep->desc)) { + spin_unlock_irqrestore(&udc->lock, flags); + status = -EINVAL; + goto err_unmap; + } + fsl_queue_td(ep, req); /* Update ep0 state */ @@ -911,6 +926,15 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) spin_unlock_irqrestore(&udc->lock, flags); return 0; + +err_unmap: + if (req->mapped) { + dma_unmap_single(udc->gadget.dev.parent, + req->req.dma, req->req.length, dir); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } + return status; } /* dequeues (cancels, unlinks) an I/O request from an endpoint */ |