[PATCH] usb: roles: try to get/put all relevant modules

Xu Yang
2024-01-12T07:54:59Z

Generally, usb role switch device will be registered by usb controller
driver. Then this usb controller device will be the parent of the usb
role switch device. And also the usb controller device will not be a
standalone device, it may be registered by other glue drivers. Currently,
the glue driver can't aware the usage of usb role switch device. So it
will remove usb controller device when the glue driver is deregistered.
In this case, below kernel dump will be shown if the user of usb role
swich (such as tcpm) tries to put it.

[ 248.891875] Hardware name: NXP i.MX95 19X19 board (DT)
[ 248.896998] pstate: 80400009 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 248.903948] pc : usb_role_switch_put+0x28/0x4c
[ 248.908385] lr : tcpm_unregister_port+0xb8/0xf8 [tcpm]
[ 248.913533] sp : ffff8000836fbbc0
[ 248.916835] x29: ffff8000836fbbc0 x28: ffff0000899fd880 x27: 0000000000000000
[ 248.923959] x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000
[ 248.931083] x23: ffff000081417970 x22: ffff00008aab10e8 x21: ffff00008aab0080
[ 248.938207] x20: ffff00008aab3110 x19: ffff00008a138c00 x18: ffffffffffffffff
[ 248.945331] x17: 0000000000000000 x16: 00e0030000000000 x15: 0000000000000000
[ 248.952455] x14: 0000000000000001 x13: 0000000000000040 x12: 0000000000000000
[ 248.959579] x11: 0000000000000000 x10: ffffffffffffffff x9 : ffff8000836fbaf0
[ 248.966703] x8 : ffff8000836fbaf0 x7 : ffff000084237728 x6 : 0000000000000400
[ 248.973827] x5 : 0000000041001000 x4 : fffffc000228ee60 x3 : 00000000820000f4
[ 248.980951] x2 : ffff00008a3b9b28 x1 : 00000000820000f5 x0 : 0000000000000000
[ 248.988076] Call trace:
[ 248.990512] usb_role_switch_put+0x28/0x4c
[ 248.994602] tcpm_unregister_port+0xb8/0xf8 [tcpm]
[ 248.999385] tcpci_remove+0x5c/0xbc [tcpci]
[ 249.003571] i2c_device_remove+0x2c/0x9c
[ 249.007489] device_remove+0x4c/0x80
[ 249.011059] device_release_driver_internal+0x1c8/0x224
[ 249.016268] driver_detach+0x50/0x98
[ 249.019830] bus_remove_driver+0x6c/0xbc
[ 249.023739] driver_unregister+0x30/0x60
[ 249.027647] i2c_del_driver+0x54/0x68
[ 249.031296] tcpci_i2c_driver_exit+0x18/0x990 [tcpci]
[ 249.036340] __arm64_sys_delete_module+0x180/0x260
[ 249.041124] invoke_syscall+0x48/0x114
[ 249.044868] el0_svc_common.constprop.0+0xc8/0xe8
[ 249.049557] do_el0_svc+0x20/0x2c
[ 249.052858] el0_svc+0x40/0xf4
[ 249.055909] el0t_64_sync_handler+0x13c/0x158
[ 249.060251] el0t_64_sync+0x190/0x194
[ 249.063904] Code: b140041f 540000e8 f9402000 f9403400 (f9400800)
[ 249.069985] ---[ end trace 0000000000000000 ]---

To fix this issue, this will try to get/put all relevant modules when the
user tries to get/put usb role switch device.

Fixes: 5c54fcac9a9d ("usb: roles: Take care of driver module reference counting")
cc: <stable@vger.kernel.org>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
drivers/usb/roles/class.c | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c
index ae41578bd014..41060e354174 100644
--- a/drivers/usb/roles/class.c
+++ b/drivers/usb/roles/class.c
@@ -34,6 +34,24 @@ struct usb_role_switch {

#define to_role_switch(d) container_of(d, struct usb_role_switch, dev)

+void usb_role_switch_get_modules(struct device *dev)
+{
+ while (dev) {
+ if (dev->driver)
+ WARN_ON(!try_module_get(dev->driver->owner));
+ dev = dev->parent;
+ }
+}
+
+void usb_role_switch_put_modules(struct device *dev)
+{
+ while (dev) {
+ if (dev->driver)
+ module_put(dev->driver->owner);
+ dev = dev->parent;
+ }
+}
+
/**
* usb_role_switch_set_role - Set USB role for a switch
* @sw: USB role switch
@@ -135,7 +153,7 @@ struct usb_role_switch *usb_role_switch_get(struct device *dev)
usb_role_switch_match);

if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
+ usb_role_switch_get_modules(sw->dev.parent);

return sw;
}
@@ -157,7 +175,7 @@ struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode)
sw = fwnode_connection_find_match(fwnode, "usb-role-switch",
NULL, usb_role_switch_match);
if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
+ usb_role_switch_get_modules(sw->dev.parent);

return sw;
}
@@ -172,7 +190,7 @@ EXPORT_SYMBOL_GPL(fwnode_usb_role_switch_get);
void usb_role_switch_put(struct usb_role_switch *sw)
{
if (!IS_ERR_OR_NULL(sw)) {
- module_put(sw->dev.parent->driver->owner);
+ usb_role_switch_put_modules(sw->dev.parent);
put_device(&sw->dev);
}
}
@@ -195,7 +213,7 @@ usb_role_switch_find_by_fwnode(const struct fwnode_handle *fwnode)

dev = class_find_device_by_fwnode(&role_class, fwnode);
if (dev)
- WARN_ON(!try_module_get(dev->parent->driver->owner));
+ usb_role_switch_get_modules(dev->parent);

return dev ? to_role_switch(dev) : NULL;
}
--
2.34.1