usb: gadget: pch_udc: Check if driver is present before calling ->setup()
authorAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Tue, 23 Mar 2021 15:36:21 +0000 (17:36 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 22 May 2021 08:40:23 +0000 (10:40 +0200)
[ Upstream commit fbdbbe6d3ee502b3bdeb4f255196bb45003614be ]

Since we have a separate routine for VBUS sense, the interrupt may occur
before gadget driver is present. Hence, ->setup() call may oops the kernel:

[   55.245843] BUG: kernel NULL pointer dereference, address: 00000010
...
[   55.245843] EIP: pch_udc_isr.cold+0x162/0x33f
...
[   55.245843]  <IRQ>
[   55.245843]  ? pch_udc_svc_data_out+0x160/0x160

Check if driver is present before calling ->setup().

Fixes: f646cf94520e ("USB device driver of Topcliff PCH")
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20210323153626.54908-2-andriy.shevchenko@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/usb/gadget/udc/pch_udc.c

index b143830285f69b42580605e93db426f280a3fe85..2363e3f666470d0765a71a5dc2586566acebe5a9 100644 (file)
@@ -2329,6 +2329,21 @@ static void pch_udc_svc_data_out(struct pch_udc_dev *dev, int ep_num)
                pch_udc_set_dma(dev, DMA_DIR_RX);
 }
 
+static int pch_udc_gadget_setup(struct pch_udc_dev *dev)
+       __must_hold(&dev->lock)
+{
+       int rc;
+
+       /* In some cases we can get an interrupt before driver gets setup */
+       if (!dev->driver)
+               return -ESHUTDOWN;
+
+       spin_unlock(&dev->lock);
+       rc = dev->driver->setup(&dev->gadget, &dev->setup_data);
+       spin_lock(&dev->lock);
+       return rc;
+}
+
 /**
  * pch_udc_svc_control_in() - Handle Control IN endpoint interrupts
  * @dev:       Reference to the device structure
@@ -2400,15 +2415,12 @@ static void pch_udc_svc_control_out(struct pch_udc_dev *dev)
                        dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IDX].ep;
                else /* OUT */
                        dev->gadget.ep0 = &ep->ep;
-               spin_lock(&dev->lock);
                /* If Mass storage Reset */
                if ((dev->setup_data.bRequestType == 0x21) &&
                    (dev->setup_data.bRequest == 0xFF))
                        dev->prot_stall = 0;
                /* call gadget with setup data received */
-               setup_supported = dev->driver->setup(&dev->gadget,
-                                                    &dev->setup_data);
-               spin_unlock(&dev->lock);
+               setup_supported = pch_udc_gadget_setup(dev);
 
                if (dev->setup_data.bRequestType & USB_DIR_IN) {
                        ep->td_data->status = (ep->td_data->status &
@@ -2656,9 +2668,7 @@ static void pch_udc_svc_intf_interrupt(struct pch_udc_dev *dev)
                dev->ep[i].halted = 0;
        }
        dev->stall = 0;
-       spin_unlock(&dev->lock);
-       dev->driver->setup(&dev->gadget, &dev->setup_data);
-       spin_lock(&dev->lock);
+       pch_udc_gadget_setup(dev);
 }
 
 /**
@@ -2693,9 +2703,7 @@ static void pch_udc_svc_cfg_interrupt(struct pch_udc_dev *dev)
        dev->stall = 0;
 
        /* call gadget zero with setup data received */
-       spin_unlock(&dev->lock);
-       dev->driver->setup(&dev->gadget, &dev->setup_data);
-       spin_lock(&dev->lock);
+       pch_udc_gadget_setup(dev);
 }
 
 /**