[PATCH] Input: serio - fix O(n^2) complexity in serio_unregister_driver()
From: Mohamad Raizudeen
Date: Wed Apr 08 2026 - 12:29:10 EST
The current implementation restarts the scan from the beginning after
each disconnection(goto start_over) because serio_disconnect_port() may
delete the current list entry. This results in O(n^2) worst case
behaviour.
Replace with a two-phase approach:
1.Collect only top-level ports bound to the driver(skip those whose parent is also bound to the same driver) into a
temporary list.
2.Process each collected port once, moving it back to serio_list first to maintain invariants expected by serio_destroy_port().
This eliminates the restart loop, reduces complexity from O(n^2) to O(n)
and avoids use-after-free by never collecting child ports separately.
Signed-off-by: Mohamad Raizudeen <raizudeen.kerneldev@xxxxxxxxx>
---
drivers/input/serio/serio.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c
index 54dd26249b02..46b8faf1a46c 100644
--- a/drivers/input/serio/serio.c
+++ b/drivers/input/serio/serio.c
@@ -821,22 +821,27 @@ EXPORT_SYMBOL(__serio_register_driver);
void serio_unregister_driver(struct serio_driver *drv)
{
- struct serio *serio;
+ struct serio *serio, *next;
+ LIST_HEAD(disconnect_list);
+
guard(mutex)(&serio_mutex);
drv->manual_bind = true; /* so serio_find_driver ignores it */
serio_remove_pending_events(drv);
-start_over:
- list_for_each_entry(serio, &serio_list, node) {
- if (serio->drv == drv) {
- serio_disconnect_port(serio);
- serio_find_driver(serio);
- /* we could've deleted some ports, restart */
- goto start_over;
+ /*Collect all ports bound to this driver first*/
+ list_for_each_entry_safe(serio, next, &serio_list, node) {
+ if (serio->drv == drv && !(serio->parent && serio->parent->drv == drv)) {
+ list_move_tail(&serio->node, &disconnect_list);
}
}
+
+ list_for_each_entry_safe(serio, next, &disconnect_list, node) {
+ list_move_tail(&serio->node, &serio_list);
+ serio_disconnect_port(serio);
+ serio_find_driver(serio);
+ }
driver_unregister(&drv->driver);
}
--
2.43.0