usb: gadget: fsl_udc: Fix a race between ep_disable and ep_queue
authorBenoit Goby <benoit@android.com>
Fri, 3 Dec 2010 00:11:15 +0000 (16:11 -0800)
committerBenoit Goby <benoit@android.com>
Fri, 3 Dec 2010 22:02:44 +0000 (14:02 -0800)
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>
drivers/usb/gadget/fsl_udc_core.c

index 20fab22cc13b50237ded9cdb71a4046348495220..2fab37a2a094a1f53eb609fee1c5041f7eca3b67 100644 (file)
@@ -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 */