Commit 0e273f9e authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'usb-4.17-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb

Pull USB fixes from Greg KH:
 "Here are some USB driver fixes fro 4.17-rc6.

  They resolve some reported bugs in the musb driver, the xhci driver,
  and a number of small fixes for the usbip driver.

  All of these have been in linux-next with no reported issues"

* tag 'usb-4.17-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb:
  usbip: usbip_host: fix bad unlock balance during stub_probe()
  usbip: usbip_host: fix NULL-ptr deref and use-after-free errors
  usbip: usbip_host: run rebind from exit when module is removed
  usbip: usbip_host: delete device from busid_table after rebind
  usbip: usbip_host: refine probe and disconnect debug msgs to be useful
  usb: musb: fix remote wakeup racing with suspend
  xhci: Fix USB3 NULL pointer dereference at logical disconnect.
parents 61c2ad9a c171654c
...@@ -354,7 +354,7 @@ int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, ...@@ -354,7 +354,7 @@ int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci,
slot_id = 0; slot_id = 0;
for (i = 0; i < MAX_HC_SLOTS; i++) { for (i = 0; i < MAX_HC_SLOTS; i++) {
if (!xhci->devs[i]) if (!xhci->devs[i] || !xhci->devs[i]->udev)
continue; continue;
speed = xhci->devs[i]->udev->speed; speed = xhci->devs[i]->udev->speed;
if (((speed >= USB_SPEED_SUPER) == (hcd->speed >= HCD_USB3)) if (((speed >= USB_SPEED_SUPER) == (hcd->speed >= HCD_USB3))
......
...@@ -2524,8 +2524,11 @@ static int musb_bus_suspend(struct usb_hcd *hcd) ...@@ -2524,8 +2524,11 @@ static int musb_bus_suspend(struct usb_hcd *hcd)
{ {
struct musb *musb = hcd_to_musb(hcd); struct musb *musb = hcd_to_musb(hcd);
u8 devctl; u8 devctl;
int ret;
musb_port_suspend(musb, true); ret = musb_port_suspend(musb, true);
if (ret)
return ret;
if (!is_host_active(musb)) if (!is_host_active(musb))
return 0; return 0;
......
...@@ -67,7 +67,7 @@ extern void musb_host_rx(struct musb *, u8); ...@@ -67,7 +67,7 @@ extern void musb_host_rx(struct musb *, u8);
extern void musb_root_disconnect(struct musb *musb); extern void musb_root_disconnect(struct musb *musb);
extern void musb_host_resume_root_hub(struct musb *musb); extern void musb_host_resume_root_hub(struct musb *musb);
extern void musb_host_poke_root_hub(struct musb *musb); extern void musb_host_poke_root_hub(struct musb *musb);
extern void musb_port_suspend(struct musb *musb, bool do_suspend); extern int musb_port_suspend(struct musb *musb, bool do_suspend);
extern void musb_port_reset(struct musb *musb, bool do_reset); extern void musb_port_reset(struct musb *musb, bool do_reset);
extern void musb_host_finish_resume(struct work_struct *work); extern void musb_host_finish_resume(struct work_struct *work);
#else #else
...@@ -99,7 +99,10 @@ static inline void musb_root_disconnect(struct musb *musb) {} ...@@ -99,7 +99,10 @@ static inline void musb_root_disconnect(struct musb *musb) {}
static inline void musb_host_resume_root_hub(struct musb *musb) {} static inline void musb_host_resume_root_hub(struct musb *musb) {}
static inline void musb_host_poll_rh_status(struct musb *musb) {} static inline void musb_host_poll_rh_status(struct musb *musb) {}
static inline void musb_host_poke_root_hub(struct musb *musb) {} static inline void musb_host_poke_root_hub(struct musb *musb) {}
static inline void musb_port_suspend(struct musb *musb, bool do_suspend) {} static inline int musb_port_suspend(struct musb *musb, bool do_suspend)
{
return 0;
}
static inline void musb_port_reset(struct musb *musb, bool do_reset) {} static inline void musb_port_reset(struct musb *musb, bool do_reset) {}
static inline void musb_host_finish_resume(struct work_struct *work) {} static inline void musb_host_finish_resume(struct work_struct *work) {}
#endif #endif
......
...@@ -48,14 +48,14 @@ void musb_host_finish_resume(struct work_struct *work) ...@@ -48,14 +48,14 @@ void musb_host_finish_resume(struct work_struct *work)
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
} }
void musb_port_suspend(struct musb *musb, bool do_suspend) int musb_port_suspend(struct musb *musb, bool do_suspend)
{ {
struct usb_otg *otg = musb->xceiv->otg; struct usb_otg *otg = musb->xceiv->otg;
u8 power; u8 power;
void __iomem *mbase = musb->mregs; void __iomem *mbase = musb->mregs;
if (!is_host_active(musb)) if (!is_host_active(musb))
return; return 0;
/* NOTE: this doesn't necessarily put PHY into low power mode, /* NOTE: this doesn't necessarily put PHY into low power mode,
* turning off its clock; that's a function of PHY integration and * turning off its clock; that's a function of PHY integration and
...@@ -66,16 +66,20 @@ void musb_port_suspend(struct musb *musb, bool do_suspend) ...@@ -66,16 +66,20 @@ void musb_port_suspend(struct musb *musb, bool do_suspend)
if (do_suspend) { if (do_suspend) {
int retries = 10000; int retries = 10000;
power &= ~MUSB_POWER_RESUME; if (power & MUSB_POWER_RESUME)
power |= MUSB_POWER_SUSPENDM; return -EBUSY;
musb_writeb(mbase, MUSB_POWER, power);
/* Needed for OPT A tests */ if (!(power & MUSB_POWER_SUSPENDM)) {
power = musb_readb(mbase, MUSB_POWER); power |= MUSB_POWER_SUSPENDM;
while (power & MUSB_POWER_SUSPENDM) { musb_writeb(mbase, MUSB_POWER, power);
/* Needed for OPT A tests */
power = musb_readb(mbase, MUSB_POWER); power = musb_readb(mbase, MUSB_POWER);
if (retries-- < 1) while (power & MUSB_POWER_SUSPENDM) {
break; power = musb_readb(mbase, MUSB_POWER);
if (retries-- < 1)
break;
}
} }
musb_dbg(musb, "Root port suspended, power %02x", power); musb_dbg(musb, "Root port suspended, power %02x", power);
...@@ -111,6 +115,7 @@ void musb_port_suspend(struct musb *musb, bool do_suspend) ...@@ -111,6 +115,7 @@ void musb_port_suspend(struct musb *musb, bool do_suspend)
schedule_delayed_work(&musb->finish_resume_work, schedule_delayed_work(&musb->finish_resume_work,
msecs_to_jiffies(USB_RESUME_TIMEOUT)); msecs_to_jiffies(USB_RESUME_TIMEOUT));
} }
return 0;
} }
void musb_port_reset(struct musb *musb, bool do_reset) void musb_port_reset(struct musb *musb, bool do_reset)
......
...@@ -73,6 +73,7 @@ struct bus_id_priv { ...@@ -73,6 +73,7 @@ struct bus_id_priv {
struct stub_device *sdev; struct stub_device *sdev;
struct usb_device *udev; struct usb_device *udev;
char shutdown_busid; char shutdown_busid;
spinlock_t busid_lock;
}; };
/* stub_priv is allocated from stub_priv_cache */ /* stub_priv is allocated from stub_priv_cache */
...@@ -83,6 +84,7 @@ extern struct usb_device_driver stub_driver; ...@@ -83,6 +84,7 @@ extern struct usb_device_driver stub_driver;
/* stub_main.c */ /* stub_main.c */
struct bus_id_priv *get_busid_priv(const char *busid); struct bus_id_priv *get_busid_priv(const char *busid);
void put_busid_priv(struct bus_id_priv *bid);
int del_match_busid(char *busid); int del_match_busid(char *busid);
void stub_device_cleanup_urbs(struct stub_device *sdev); void stub_device_cleanup_urbs(struct stub_device *sdev);
......
...@@ -300,9 +300,9 @@ static int stub_probe(struct usb_device *udev) ...@@ -300,9 +300,9 @@ static int stub_probe(struct usb_device *udev)
struct stub_device *sdev = NULL; struct stub_device *sdev = NULL;
const char *udev_busid = dev_name(&udev->dev); const char *udev_busid = dev_name(&udev->dev);
struct bus_id_priv *busid_priv; struct bus_id_priv *busid_priv;
int rc; int rc = 0;
dev_dbg(&udev->dev, "Enter\n"); dev_dbg(&udev->dev, "Enter probe\n");
/* check we should claim or not by busid_table */ /* check we should claim or not by busid_table */
busid_priv = get_busid_priv(udev_busid); busid_priv = get_busid_priv(udev_busid);
...@@ -317,13 +317,15 @@ static int stub_probe(struct usb_device *udev) ...@@ -317,13 +317,15 @@ static int stub_probe(struct usb_device *udev)
* other matched drivers by the driver core. * other matched drivers by the driver core.
* See driver_probe_device() in driver/base/dd.c * See driver_probe_device() in driver/base/dd.c
*/ */
return -ENODEV; rc = -ENODEV;
goto call_put_busid_priv;
} }
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) { if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) {
dev_dbg(&udev->dev, "%s is a usb hub device... skip!\n", dev_dbg(&udev->dev, "%s is a usb hub device... skip!\n",
udev_busid); udev_busid);
return -ENODEV; rc = -ENODEV;
goto call_put_busid_priv;
} }
if (!strcmp(udev->bus->bus_name, "vhci_hcd")) { if (!strcmp(udev->bus->bus_name, "vhci_hcd")) {
...@@ -331,13 +333,16 @@ static int stub_probe(struct usb_device *udev) ...@@ -331,13 +333,16 @@ static int stub_probe(struct usb_device *udev)
"%s is attached on vhci_hcd... skip!\n", "%s is attached on vhci_hcd... skip!\n",
udev_busid); udev_busid);
return -ENODEV; rc = -ENODEV;
goto call_put_busid_priv;
} }
/* ok, this is my device */ /* ok, this is my device */
sdev = stub_device_alloc(udev); sdev = stub_device_alloc(udev);
if (!sdev) if (!sdev) {
return -ENOMEM; rc = -ENOMEM;
goto call_put_busid_priv;
}
dev_info(&udev->dev, dev_info(&udev->dev,
"usbip-host: register new device (bus %u dev %u)\n", "usbip-host: register new device (bus %u dev %u)\n",
...@@ -369,7 +374,9 @@ static int stub_probe(struct usb_device *udev) ...@@ -369,7 +374,9 @@ static int stub_probe(struct usb_device *udev)
} }
busid_priv->status = STUB_BUSID_ALLOC; busid_priv->status = STUB_BUSID_ALLOC;
return 0; rc = 0;
goto call_put_busid_priv;
err_files: err_files:
usb_hub_release_port(udev->parent, udev->portnum, usb_hub_release_port(udev->parent, udev->portnum,
(struct usb_dev_state *) udev); (struct usb_dev_state *) udev);
...@@ -379,6 +386,9 @@ static int stub_probe(struct usb_device *udev) ...@@ -379,6 +386,9 @@ static int stub_probe(struct usb_device *udev)
busid_priv->sdev = NULL; busid_priv->sdev = NULL;
stub_device_free(sdev); stub_device_free(sdev);
call_put_busid_priv:
put_busid_priv(busid_priv);
return rc; return rc;
} }
...@@ -404,7 +414,7 @@ static void stub_disconnect(struct usb_device *udev) ...@@ -404,7 +414,7 @@ static void stub_disconnect(struct usb_device *udev)
struct bus_id_priv *busid_priv; struct bus_id_priv *busid_priv;
int rc; int rc;
dev_dbg(&udev->dev, "Enter\n"); dev_dbg(&udev->dev, "Enter disconnect\n");
busid_priv = get_busid_priv(udev_busid); busid_priv = get_busid_priv(udev_busid);
if (!busid_priv) { if (!busid_priv) {
...@@ -417,7 +427,7 @@ static void stub_disconnect(struct usb_device *udev) ...@@ -417,7 +427,7 @@ static void stub_disconnect(struct usb_device *udev)
/* get stub_device */ /* get stub_device */
if (!sdev) { if (!sdev) {
dev_err(&udev->dev, "could not get device"); dev_err(&udev->dev, "could not get device");
return; goto call_put_busid_priv;
} }
dev_set_drvdata(&udev->dev, NULL); dev_set_drvdata(&udev->dev, NULL);
...@@ -432,12 +442,12 @@ static void stub_disconnect(struct usb_device *udev) ...@@ -432,12 +442,12 @@ static void stub_disconnect(struct usb_device *udev)
(struct usb_dev_state *) udev); (struct usb_dev_state *) udev);
if (rc) { if (rc) {
dev_dbg(&udev->dev, "unable to release port\n"); dev_dbg(&udev->dev, "unable to release port\n");
return; goto call_put_busid_priv;
} }
/* If usb reset is called from event handler */ /* If usb reset is called from event handler */
if (usbip_in_eh(current)) if (usbip_in_eh(current))
return; goto call_put_busid_priv;
/* shutdown the current connection */ /* shutdown the current connection */
shutdown_busid(busid_priv); shutdown_busid(busid_priv);
...@@ -448,12 +458,11 @@ static void stub_disconnect(struct usb_device *udev) ...@@ -448,12 +458,11 @@ static void stub_disconnect(struct usb_device *udev)
busid_priv->sdev = NULL; busid_priv->sdev = NULL;
stub_device_free(sdev); stub_device_free(sdev);
if (busid_priv->status == STUB_BUSID_ALLOC) { if (busid_priv->status == STUB_BUSID_ALLOC)
busid_priv->status = STUB_BUSID_ADDED; busid_priv->status = STUB_BUSID_ADDED;
} else {
busid_priv->status = STUB_BUSID_OTHER; call_put_busid_priv:
del_match_busid((char *)udev_busid); put_busid_priv(busid_priv);
}
} }
#ifdef CONFIG_PM #ifdef CONFIG_PM
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#define DRIVER_DESC "USB/IP Host Driver" #define DRIVER_DESC "USB/IP Host Driver"
struct kmem_cache *stub_priv_cache; struct kmem_cache *stub_priv_cache;
/* /*
* busid_tables defines matching busids that usbip can grab. A user can change * busid_tables defines matching busids that usbip can grab. A user can change
* dynamically what device is locally used and what device is exported to a * dynamically what device is locally used and what device is exported to a
...@@ -25,6 +26,8 @@ static spinlock_t busid_table_lock; ...@@ -25,6 +26,8 @@ static spinlock_t busid_table_lock;
static void init_busid_table(void) static void init_busid_table(void)
{ {
int i;
/* /*
* This also sets the bus_table[i].status to * This also sets the bus_table[i].status to
* STUB_BUSID_OTHER, which is 0. * STUB_BUSID_OTHER, which is 0.
...@@ -32,6 +35,9 @@ static void init_busid_table(void) ...@@ -32,6 +35,9 @@ static void init_busid_table(void)
memset(busid_table, 0, sizeof(busid_table)); memset(busid_table, 0, sizeof(busid_table));
spin_lock_init(&busid_table_lock); spin_lock_init(&busid_table_lock);
for (i = 0; i < MAX_BUSID; i++)
spin_lock_init(&busid_table[i].busid_lock);
} }
/* /*
...@@ -43,15 +49,20 @@ static int get_busid_idx(const char *busid) ...@@ -43,15 +49,20 @@ static int get_busid_idx(const char *busid)
int i; int i;
int idx = -1; int idx = -1;
for (i = 0; i < MAX_BUSID; i++) for (i = 0; i < MAX_BUSID; i++) {
spin_lock(&busid_table[i].busid_lock);
if (busid_table[i].name[0]) if (busid_table[i].name[0])
if (!strncmp(busid_table[i].name, busid, BUSID_SIZE)) { if (!strncmp(busid_table[i].name, busid, BUSID_SIZE)) {
idx = i; idx = i;
spin_unlock(&busid_table[i].busid_lock);
break; break;
} }
spin_unlock(&busid_table[i].busid_lock);
}
return idx; return idx;
} }
/* Returns holding busid_lock. Should call put_busid_priv() to unlock */
struct bus_id_priv *get_busid_priv(const char *busid) struct bus_id_priv *get_busid_priv(const char *busid)
{ {
int idx; int idx;
...@@ -59,13 +70,22 @@ struct bus_id_priv *get_busid_priv(const char *busid) ...@@ -59,13 +70,22 @@ struct bus_id_priv *get_busid_priv(const char *busid)
spin_lock(&busid_table_lock); spin_lock(&busid_table_lock);
idx = get_busid_idx(busid); idx = get_busid_idx(busid);
if (idx >= 0) if (idx >= 0) {
bid = &(busid_table[idx]); bid = &(busid_table[idx]);
/* get busid_lock before returning */
spin_lock(&bid->busid_lock);
}
spin_unlock(&busid_table_lock); spin_unlock(&busid_table_lock);
return bid; return bid;
} }
void put_busid_priv(struct bus_id_priv *bid)
{
if (bid)
spin_unlock(&bid->busid_lock);
}
static int add_match_busid(char *busid) static int add_match_busid(char *busid)
{ {
int i; int i;
...@@ -78,15 +98,19 @@ static int add_match_busid(char *busid) ...@@ -78,15 +98,19 @@ static int add_match_busid(char *busid)
goto out; goto out;
} }
for (i = 0; i < MAX_BUSID; i++) for (i = 0; i < MAX_BUSID; i++) {
spin_lock(&busid_table[i].busid_lock);
if (!busid_table[i].name[0]) { if (!busid_table[i].name[0]) {
strlcpy(busid_table[i].name, busid, BUSID_SIZE); strlcpy(busid_table[i].name, busid, BUSID_SIZE);
if ((busid_table[i].status != STUB_BUSID_ALLOC) && if ((busid_table[i].status != STUB_BUSID_ALLOC) &&
(busid_table[i].status != STUB_BUSID_REMOV)) (busid_table[i].status != STUB_BUSID_REMOV))
busid_table[i].status = STUB_BUSID_ADDED; busid_table[i].status = STUB_BUSID_ADDED;
ret = 0; ret = 0;
spin_unlock(&busid_table[i].busid_lock);
break; break;
} }
spin_unlock(&busid_table[i].busid_lock);
}
out: out:
spin_unlock(&busid_table_lock); spin_unlock(&busid_table_lock);
...@@ -107,6 +131,8 @@ int del_match_busid(char *busid) ...@@ -107,6 +131,8 @@ int del_match_busid(char *busid)
/* found */ /* found */
ret = 0; ret = 0;
spin_lock(&busid_table[idx].busid_lock);
if (busid_table[idx].status == STUB_BUSID_OTHER) if (busid_table[idx].status == STUB_BUSID_OTHER)
memset(busid_table[idx].name, 0, BUSID_SIZE); memset(busid_table[idx].name, 0, BUSID_SIZE);
...@@ -114,6 +140,7 @@ int del_match_busid(char *busid) ...@@ -114,6 +140,7 @@ int del_match_busid(char *busid)
(busid_table[idx].status != STUB_BUSID_ADDED)) (busid_table[idx].status != STUB_BUSID_ADDED))
busid_table[idx].status = STUB_BUSID_REMOV; busid_table[idx].status = STUB_BUSID_REMOV;
spin_unlock(&busid_table[idx].busid_lock);
out: out:
spin_unlock(&busid_table_lock); spin_unlock(&busid_table_lock);
...@@ -126,9 +153,12 @@ static ssize_t match_busid_show(struct device_driver *drv, char *buf) ...@@ -126,9 +153,12 @@ static ssize_t match_busid_show(struct device_driver *drv, char *buf)
char *out = buf; char *out = buf;
spin_lock(&busid_table_lock); spin_lock(&busid_table_lock);
for (i = 0; i < MAX_BUSID; i++) for (i = 0; i < MAX_BUSID; i++) {
spin_lock(&busid_table[i].busid_lock);
if (busid_table[i].name[0]) if (busid_table[i].name[0])
out += sprintf(out, "%s ", busid_table[i].name); out += sprintf(out, "%s ", busid_table[i].name);
spin_unlock(&busid_table[i].busid_lock);
}
spin_unlock(&busid_table_lock); spin_unlock(&busid_table_lock);
out += sprintf(out, "\n"); out += sprintf(out, "\n");
...@@ -169,6 +199,51 @@ static ssize_t match_busid_store(struct device_driver *dev, const char *buf, ...@@ -169,6 +199,51 @@ static ssize_t match_busid_store(struct device_driver *dev, const char *buf,
} }
static DRIVER_ATTR_RW(match_busid); static DRIVER_ATTR_RW(match_busid);
static int do_rebind(char *busid, struct bus_id_priv *busid_priv)
{
int ret;
/* device_attach() callers should hold parent lock for USB */
if (busid_priv->udev->dev.parent)
device_lock(busid_priv->udev->dev.parent);
ret = device_attach(&busid_priv->udev->dev);
if (busid_priv->udev->dev.parent)
device_unlock(busid_priv->udev->dev.parent);
if (ret < 0) {
dev_err(&busid_priv->udev->dev, "rebind failed\n");
return ret;
}
return 0;
}
static void stub_device_rebind(void)
{
#if IS_MODULE(CONFIG_USBIP_HOST)
struct bus_id_priv *busid_priv;
int i;
/* update status to STUB_BUSID_OTHER so probe ignores the device */
spin_lock(&busid_table_lock);
for (i = 0; i < MAX_BUSID; i++) {
if (busid_table[i].name[0] &&
busid_table[i].shutdown_busid) {
busid_priv = &(busid_table[i]);
busid_priv->status = STUB_BUSID_OTHER;
}
}
spin_unlock(&busid_table_lock);
/* now run rebind - no need to hold locks. driver files are removed */
for (i = 0; i < MAX_BUSID; i++) {
if (busid_table[i].name[0] &&
busid_table[i].shutdown_busid) {
busid_priv = &(busid_table[i]);
do_rebind(busid_table[i].name, busid_priv);
}
}
#endif
}
static ssize_t rebind_store(struct device_driver *dev, const char *buf, static ssize_t rebind_store(struct device_driver *dev, const char *buf,
size_t count) size_t count)
{ {
...@@ -186,16 +261,17 @@ static ssize_t rebind_store(struct device_driver *dev, const char *buf, ...@@ -186,16 +261,17 @@ static ssize_t rebind_store(struct device_driver *dev, const char *buf,
if (!bid) if (!bid)
return -ENODEV; return -ENODEV;
/* device_attach() callers should hold parent lock for USB */ /* mark the device for deletion so probe ignores it during rescan */
if (bid->udev->dev.parent) bid->status = STUB_BUSID_OTHER;
device_lock(bid->udev->dev.parent); /* release the busid lock */
ret = device_attach(&bid->udev->dev); put_busid_priv(bid);
if (bid->udev->dev.parent)
device_unlock(bid->udev->dev.parent); ret = do_rebind((char *) buf, bid);
if (ret < 0) { if (ret < 0)
dev_err(&bid->udev->dev, "rebind failed\n");
return ret; return ret;
}
/* delete device from busid_table */
del_match_busid((char *) buf);
return count; return count;
} }
...@@ -317,6 +393,9 @@ static void __exit usbip_host_exit(void) ...@@ -317,6 +393,9 @@ static void __exit usbip_host_exit(void)
*/ */
usb_deregister_device_driver(&stub_driver); usb_deregister_device_driver(&stub_driver);
/* initiate scan to attach devices */
stub_device_rebind();
kmem_cache_destroy(stub_priv_cache); kmem_cache_destroy(stub_priv_cache);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment