* [PATCH v5 1/2] usb: core: export the generic device driver's operations
2026-11-07 2:36 [PATCH v5 0/2] usb: misc: onboard_usb_dev: don't defer probing after the device is configured James Clark
@ 2026-11-07 2:36 ` James Clark
2026-11-07 2:36 ` [PATCH v5 2/2] usb: misc: onboard_usb_dev: don't defer probing after the device is configured James Clark
1 sibling, 0 replies; 3+ messages in thread
From: James Clark @ 2026-11-07 2:36 UTC (permalink / raw)
To: Greg Kroah-Hartman, Alan Stern, Matthias Kaehlcke
Cc: Johan Hovold, Bastien Nocera, Arnd Bergmann, Bartosz Golaszewski,
Douglas Anderson, Robert Nelson, linux-usb, linux-kernel,
linux-gpio, regressions
Export usb_generic_driver_probe(), usb_generic_driver_disconnect(),
usb_generic_driver_suspend() and usb_generic_driver_resume(), and move
their declarations to include/linux/usb.h. This lets a driver whose
probe may return -EPROBE_DEFER leave generic_subclass unset and call
them itself, so that it can defer before the device is configured.
Say in the generic_subclass kerneldoc not to set it in that case.
Signed-off-by: James Clark <jjc@jclark.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
---
Changes in v5:
- Added Alan's Acked-by
Changes in v4:
- New patch
drivers/usb/core/generic.c | 4 ++++
drivers/usb/core/usb.h | 6 ------
include/linux/usb.h | 10 +++++++++-
3 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index a48994e11ef3..53675d9fdb03 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -261,6 +261,7 @@ int usb_generic_driver_probe(struct usb_device *udev)
return 0;
}
+EXPORT_SYMBOL_GPL(usb_generic_driver_probe);
void usb_generic_driver_disconnect(struct usb_device *udev)
{
@@ -271,6 +272,7 @@ void usb_generic_driver_disconnect(struct usb_device *udev)
if (udev->actconfig)
usb_set_configuration(udev, -1);
}
+EXPORT_SYMBOL_GPL(usb_generic_driver_disconnect);
#ifdef CONFIG_PM
@@ -301,6 +303,7 @@ int usb_generic_driver_suspend(struct usb_device *udev, pm_message_t msg)
usbfs_notify_suspend(udev);
return rc;
}
+EXPORT_SYMBOL_GPL(usb_generic_driver_suspend);
int usb_generic_driver_resume(struct usb_device *udev, pm_message_t msg)
{
@@ -320,6 +323,7 @@ int usb_generic_driver_resume(struct usb_device *udev, pm_message_t msg)
usbfs_notify_resume(udev);
return rc;
}
+EXPORT_SYMBOL_GPL(usb_generic_driver_resume);
#endif /* CONFIG_PM */
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index a9b37aeb515b..9f1e4d197695 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -50,12 +50,6 @@ extern int usb_get_bos_descriptor(struct usb_device *dev);
extern void usb_release_bos_descriptor(struct usb_device *dev);
extern int usb_set_configuration(struct usb_device *dev, int configuration);
extern int usb_choose_configuration(struct usb_device *udev);
-extern int usb_generic_driver_probe(struct usb_device *udev);
-extern void usb_generic_driver_disconnect(struct usb_device *udev);
-extern int usb_generic_driver_suspend(struct usb_device *udev,
- pm_message_t msg);
-extern int usb_generic_driver_resume(struct usb_device *udev,
- pm_message_t msg);
static inline unsigned usb_get_max_power(struct usb_device *udev,
struct usb_host_config *c)
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 825f03b29067..4844f53e2fa8 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1301,7 +1301,8 @@ struct usb_driver {
* for devices bound to this driver.
* @generic_subclass: if set to 1, the generic USB driver's probe, disconnect,
* resume and suspend functions will be called in addition to the driver's
- * own, so this part of the setup does not need to be replicated.
+ * own, so this part of the setup does not need to be replicated. Do not set
+ * this if @probe may return -EPROBE_DEFER.
*
* USB device drivers must provide a name, other driver fields are optional.
*/
@@ -1375,6 +1376,13 @@ extern int __usb_register_device_driver(struct usb_device_driver *,
struct module *, const char *);
extern void usb_deregister_device_driver(struct usb_device_driver *);
+extern int usb_generic_driver_probe(struct usb_device *udev);
+extern void usb_generic_driver_disconnect(struct usb_device *udev);
+extern int usb_generic_driver_suspend(struct usb_device *udev,
+ pm_message_t msg);
+extern int usb_generic_driver_resume(struct usb_device *udev,
+ pm_message_t msg);
+
extern int usb_register_dev(struct usb_interface *intf,
struct usb_class_driver *class_driver);
extern void usb_deregister_dev(struct usb_interface *intf,
--
2.56.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH v5 2/2] usb: misc: onboard_usb_dev: don't defer probing after the device is configured
2026-11-07 2:36 [PATCH v5 0/2] usb: misc: onboard_usb_dev: don't defer probing after the device is configured James Clark
2026-11-07 2:36 ` [PATCH v5 1/2] usb: core: export the generic device driver's operations James Clark
@ 2026-11-07 2:36 ` James Clark
1 sibling, 0 replies; 3+ messages in thread
From: James Clark @ 2026-11-07 2:36 UTC (permalink / raw)
To: Greg Kroah-Hartman, Alan Stern, Matthias Kaehlcke
Cc: Johan Hovold, Bastien Nocera, Arnd Bergmann, Bartosz Golaszewski,
Douglas Anderson, Robert Nelson, linux-usb, linux-kernel,
linux-gpio, regressions
onboard_usb_dev breaks the rule that a driver must not return
-EPROBE_DEFER from its probe after it has registered child devices (see
commit fbc35b45f9f6 ("Add documentation on meaning of -EPROBE_DEFER")).
Its USB device driver sets generic_subclass, so the generic probe runs
first and registers the device's interfaces. onboard_dev_usbdev_probe()
then returns -EPROBE_DEFER until the platform device for the onboard
device has probed. On the BeagleY-AI, this results in a probe loop,
which has broken booting since commit f918e3804498 ("arm64: enable
HAVE_SHARED_GPIOS for all ARM64 platforms").
Call the generic driver's operations from the driver itself instead of
relying on generic_subclass, so that the probe is deferred before the
device is configured. The device now stays unconfigured while the probe
is deferred, including when the platform device never probes. Before, it
was configured and unconfigured repeatedly until the platform device
probed.
Make onboard_dev_usbdev_match() not match a device when neither its
node nor its peer hub's node has a platform device, so that the generic
driver binds it instead. If the platform device goes away between match
and probe, return -ENODEV instead of -EINVAL, so that usb_probe_device()
falls back to the generic driver.
Fixes: 8bc063641ceb ("usb: misc: Add onboard_usb_hub driver")
Suggested-by: Johan Hovold <johan@kernel.org>
Signed-off-by: James Clark <jjc@jclark.com>
---
Changes in v5:
- Stopped matching a device when neither its node nor its peer hub's
node has a platform device, so that the generic driver binds it
(Sashiko)
- Split the platform device lookup out of _find_onboard_dev() into
onboard_dev_find_pdev(), and dropped the "failed to find device node
for peer hub" error
- Commit message: explained the -ENODEV
Changes in v4:
- Switched to calling the generic driver's operations instead of
setting generic_subclass, replacing pre_probe in the USB core
drivers/usb/misc/onboard_usb_dev.c | 67 +++++++++++++++++++++---------
1 file changed, 48 insertions(+), 19 deletions(-)
diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c
index c60406240416..299e39b55e73 100644
--- a/drivers/usb/misc/onboard_usb_dev.c
+++ b/drivers/usb/misc/onboard_usb_dev.c
@@ -576,6 +576,25 @@ static struct platform_driver onboard_dev_driver = {
#define VENDOR_ID_VIA 0x2109
#define VENDOR_ID_XMOS 0x20B1
+static struct platform_device *onboard_dev_find_pdev(struct device *dev)
+{
+ struct platform_device *pdev;
+ struct device_node *np;
+
+ pdev = of_find_device_by_node(dev->of_node);
+ if (pdev)
+ return pdev;
+
+ np = of_parse_phandle(dev->of_node, "peer-hub", 0);
+ if (!np)
+ return NULL;
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+
+ return pdev;
+}
+
/*
* Returns the onboard_dev platform device that is associated with the USB
* device passed as parameter.
@@ -583,23 +602,11 @@ static struct platform_driver onboard_dev_driver = {
static struct onboard_dev *_find_onboard_dev(struct device *dev)
{
struct platform_device *pdev;
- struct device_node *np;
struct onboard_dev *onboard_dev;
- pdev = of_find_device_by_node(dev->of_node);
- if (!pdev) {
- np = of_parse_phandle(dev->of_node, "peer-hub", 0);
- if (!np) {
- dev_err(dev, "failed to find device node for peer hub\n");
- return ERR_PTR(-EINVAL);
- }
-
- pdev = of_find_device_by_node(np);
- of_node_put(np);
-
- if (!pdev)
- return ERR_PTR(-ENODEV);
- }
+ pdev = onboard_dev_find_pdev(dev);
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
onboard_dev = dev_get_drvdata(&pdev->dev);
put_device(&pdev->dev);
@@ -619,8 +626,22 @@ static struct onboard_dev *_find_onboard_dev(struct device *dev)
static bool onboard_dev_usbdev_match(struct usb_device *udev)
{
- /* Onboard devices using this driver must have a device tree node */
- return !!udev->dev.of_node;
+ struct platform_device *pdev;
+
+ /*
+ * Onboard devices using this driver must have a device tree node with
+ * a platform device, its own or its peer hub's. Leave any other device
+ * to the generic driver.
+ */
+ if (!udev->dev.of_node)
+ return false;
+
+ pdev = onboard_dev_find_pdev(&udev->dev);
+ if (!pdev)
+ return false;
+
+ put_device(&pdev->dev);
+ return true;
}
static int onboard_dev_usbdev_probe(struct usb_device *udev)
@@ -639,7 +660,11 @@ static int onboard_dev_usbdev_probe(struct usb_device *udev)
if (err)
return err;
- return 0;
+ err = usb_generic_driver_probe(udev);
+ if (err)
+ onboard_dev_remove_usbdev(onboard_dev, udev);
+
+ return err;
}
static void onboard_dev_usbdev_disconnect(struct usb_device *udev)
@@ -647,6 +672,7 @@ static void onboard_dev_usbdev_disconnect(struct usb_device *udev)
struct onboard_dev *onboard_dev = dev_get_drvdata(&udev->dev);
onboard_dev_remove_usbdev(onboard_dev, udev);
+ usb_generic_driver_disconnect(udev);
}
static const struct usb_device_id onboard_dev_id_table[] = {
@@ -697,7 +723,10 @@ static struct usb_device_driver onboard_dev_usbdev_driver = {
.match = onboard_dev_usbdev_match,
.probe = onboard_dev_usbdev_probe,
.disconnect = onboard_dev_usbdev_disconnect,
- .generic_subclass = 1,
+#ifdef CONFIG_PM
+ .suspend = usb_generic_driver_suspend,
+ .resume = usb_generic_driver_resume,
+#endif
.supports_autosuspend = 1,
.id_table = onboard_dev_id_table,
};
--
2.56.0
^ permalink raw reply [flat|nested] 3+ messages in thread