usb: host: xhci-tegra: Unregister OTG notifier before cancelling id_work
Subsystems (4)
PATCH 1/1 usb: host: xhci-tegra: Unregister OTG notifier before cancelling id_work
tegra_xusb_remove() cancels id_work, then calls otg_set_host() with a NULL host. That leaves id_nb registered until devm_usb_phy_release2() runs after .remove() returns, so a role change can queue id_work again and use the HCD released by usb_put_hcd().
Unregister id_nb first. atomic_notifier_chain_unregister() finishes callbacks already on the chain, and cancel_work_sync() drains the work they queued. devm_usb_phy_release2() unregisters again. A second unregister finds no entry; the wrapper discards the internal -ENOENT. The devres entry is still required for usb_put_phy().
This issue was identified during our ongoing static-analysis research while reviewing kernel code.
--- drivers/usb/host/xhci-tegra.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
drivers/usb/host/xhci-tegra.c +13 -4
@@ -1542,11 +1542,20 @@ static void tegra_xusb_deinit_usb_phy(struct tegra_xusb *tegra) {unsigned int i; -cancel_work_sync (&tegra->id_work); - -for (i =0 ; i < tegra->num_usb_phys; i++) -if (tegra->usbphy[i]) +/* +* id_nb is registered on the PHY notifier chain by +* devm_usb_get_phy_by_node(), so devres would only drop it after +* tegra_xusb_remove() has returned. Unregister it here, before the +* work is cancelled, so that it cannot be queued again. +*/ +for (i =0 ; i < tegra->num_usb_phys; i++) { +if (tegra->usbphy[i]) { +usb_unregister_notifier (tegra->usbphy[i], &tegra->id_nb);otg_set_host (tegra->usbphy[i]->otg,NULL ); + } + } + +cancel_work_sync (&tegra->id_work); }static int tegra_xusb_setup_wakeup (struct platform_device *pdev,struct tegra_xusb *tegra)
base-commit: 238650ef6c7c7cca08e032527329424c9fbd70e5
Signature · 1 line
2.53.0