FAILED: patch "[PATCH] usb: typec: ucsi: Fix race condition and ordering in port" failed to apply to 6.12-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026072726-limit-wikipedia-5852@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261 Mon Sep 17 00:00:00 2001
From: Andrei Kuchynski <[email protected]>
Date: Tue, 7 Jul 2026 14:17:36 +0000
Subject: [PATCH] usb: typec: ucsi: Fix race condition and ordering in port
unregistration
A synchronization issue exists during port unregistration where pending partner work items can race against workqueue destruction, leading to use-after-free conditions:
cros_ec_ucsi cros_ec_ucsi.3.auto: error -ETIMEDOUT: PPM init failed
BUG: kernel NULL pointer dereference, address: 0000000000000000
RIP: 0010:__queue_work+0x83/0x4a0
Call Trace:
<IRQ>
__cfi_delayed_work_timer_fn+0x10/0x10
run_timer_softirq+0x3b6/0xbd0
sched_clock_cpu+0xc/0x110
irq_exit_rcu+0x18d/0x330
fred_sysvec_apic_timer_interrupt+0x5e/0x80Fix this by ensuring strict ordering and proper serialization during teardown:
1. Move ucsi_unregister_partner() to the beginning of the teardown sequence and protect it under the connector mutex lock.
2. Ensure all pending partner tasks are explicitly flushed and finished before the workqueue is destroyed.
3. Switch from mod_delayed_work() to a cancel_delayed_work() and queue_delayed_work() sequence. This guarantees that items currently marked as pending won't be scheduled an additional time, preventing a double release of resources which leads to the following crash:
Oops: general protection fault, probably for non-canonical address
0xdead000000000122: 0000 [#1] SMP NOPTI
Workqueue: cros_ec_ucsi.3.auto-con2 ucsi_poll_worker
RIP: 0010:ucsi_poll_worker+0x65/0x1e0
Call Trace:
<TASK>
process_scheduled_works+0x218/0x6d0
worker_thread+0x188/0x3f0
__cfi_worker_thread+0x10/0x10
kthread+0x226/0x2a0To ensure these rules are applied identically across both the normal teardown and the ucsi_init() error paths, consolidate the cleanup logic into a new helper, ucsi_unregister_port().
drivers/usb/typec/ucsi/ucsi.c +39 -43
@@ -1845,6 +1845,42 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)return ret; } +static void ucsi_unregister_port (struct ucsi_connector *con) +{ +struct ucsi_work *uwork; + +if (con->wq) { +mutex_lock (&con->lock); +ucsi_unregister_partner (con); +/* +* queue delayed items immediately so they can execute +* and free themselves before the wq is destroyed +*/ +list_for_each_entry (uwork, &con->partner_tasks, node) { +if (cancel_delayed_work (&uwork->work)) +queue_delayed_work (con->wq, &uwork->work,0 ); + } +mutex_unlock (&con->lock); + +destroy_workqueue (con->wq); + con->wq =NULL ; + }else { +ucsi_unregister_partner (con); + } + +ucsi_unregister_altmodes (con,UCSI_RECIPIENT_CON ); +ucsi_unregister_port_psy (con); + +usb_power_delivery_unregister_capabilities (con->port_sink_caps); + con->port_sink_caps =NULL ; +usb_power_delivery_unregister_capabilities (con->port_source_caps); + con->port_source_caps =NULL ; +usb_power_delivery_unregister (con->pd); + con->pd =NULL ; +typec_unregister_port (con->port); + con->port =NULL ; +} +static u64 ucsi_get_supported_notifications (struct ucsi *ucsi) {u16 features = ucsi->cap.features;
@@ -1971,22 +2007,8 @@ static int ucsi_init(struct ucsi *ucsi)for (i =0 ; i < ucsi->cap.num_connectors; i++)lockdep_unregister_key (&connector[i].lock_key); -for (con = connector; con->port; con++) { -if (con->wq) -destroy_workqueue (con->wq); -ucsi_unregister_partner (con); -ucsi_unregister_altmodes (con,UCSI_RECIPIENT_CON ); -ucsi_unregister_port_psy (con); - -usb_power_delivery_unregister_capabilities (con->port_sink_caps); - con->port_sink_caps =NULL ; -usb_power_delivery_unregister_capabilities (con->port_source_caps); - con->port_source_caps =NULL ; -usb_power_delivery_unregister (con->pd); - con->pd =NULL ; -typec_unregister_port (con->port); - con->port =NULL ; - } +for (con = connector; con->port; con++) +ucsi_unregister_port (con);kfree (connector); err_reset:memset (&ucsi->cap,0 ,sizeof (ucsi->cap));
@@ -2214,33 +2236,7 @@ void ucsi_unregister(struct ucsi *ucsi)for (i =0 ; i < ucsi->cap.num_connectors; i++) {cancel_work_sync (&ucsi->connector[i].work); - -if (ucsi->connector[i].wq) { -struct ucsi_work *uwork; - -mutex_lock (&ucsi->connector[i].lock); -/* -* queue delayed items immediately so they can execute -* and free themselves before the wq is destroyed -*/ -list_for_each_entry (uwork, &ucsi->connector[i].partner_tasks, node) -mod_delayed_work (ucsi->connector[i].wq, &uwork->work,0 ); -mutex_unlock (&ucsi->connector[i].lock); -destroy_workqueue (ucsi->connector[i].wq); - } - -ucsi_unregister_partner (&ucsi->connector[i]); -ucsi_unregister_altmodes (&ucsi->connector[i], -UCSI_RECIPIENT_CON ); -ucsi_unregister_port_psy (&ucsi->connector[i]); - -usb_power_delivery_unregister_capabilities (ucsi->connector[i].port_sink_caps); - ucsi->connector[i].port_sink_caps =NULL ; -usb_power_delivery_unregister_capabilities (ucsi->connector[i].port_source_caps); - ucsi->connector[i].port_source_caps =NULL ; -usb_power_delivery_unregister (ucsi->connector[i].pd); - ucsi->connector[i].pd =NULL ; -typec_unregister_port (ucsi->connector[i].port); +ucsi_unregister_port (&ucsi->connector[i]);lockdep_unregister_key (&ucsi->connector[i].lock_key); }
Sasha Levin
maintainerPATCH 1/2 usb: typec: ucsi: split connector lock classes
From: Sergey Senozhatsky <[email protected]>
[ Upstream commit 8c22256bbafad3dc5fdbe9f684d045b67ff06a68 ]
Lockdep detects a possible recursive locking scenario during ucsi init:
[ 5.418616] ============================================
[ 5.418634] WARNING: possible recursive locking detected
[ 5.418706] --------------------------------------------
[ 5.418725] kworker/4:1/82 is trying to acquire lock:
[ 5.418759] ffff888119a34648 (&con->lock){+.+.}-{3:3}, at: ucsi_init_work+0x1a78/0x2eb0 [typec_ucsi]
[ 5.418801]
but task is already holding lock:
[ 5.418835] ffff888119a34080 (&con->lock){+.+.}-{3:3}, at: ucsi_init_work+0x1a78/0x2eb0 [typec_ucsi]
[ 5.418884]
other info that might help us debug this:
[ 5.418904] Possible unsafe locking scenario:[ 5.418937] CPU0
[ 5.418956] ----
[ 5.418991] lock(&con->lock);
[ 5.419013] lock(&con->lock);
[ 5.419033]
*** DEADLOCK ***[ 5.419387] Call Trace: [ 5.419406] <TASK> [ 5.419425] dump_stack_lvl+0x61/0xa0 [ 5.419448] print_deadlock_bug+0x4a6/0x650 [ 5.419483] __lock_acquire+0x62b6/0x7f50 [ 5.419507] lock_acquire+0x11b/0x390 [ 5.419654] __mutex_lock+0xbc/0xcd0 [ 5.419741] ucsi_init_work+0x1a78/0x2eb0 [ 5.419785] ? worker_thread+0xf53/0x2bc0 [ 5.419819] worker_thread+0xff4/0x2bc0 [ 5.419842] kthread+0x2a7/0x330 [ 5.419863] ? __pfx_worker_thread+0x10/0x10 [ 5.419896] ? __pfx_kthread+0x10/0x10 [ 5.419916] ret_from_fork+0x38/0x70 [ 5.419936] ? __pfx_kthread+0x10/0x10 [ 5.419969] ret_from_fork_asm+0x1b/0x30 [ 5.419991] </TASK> [ 5.420009] ---[ end trace 0000000000000000 ]---
The problem is that all connector locks belong to the same lockdep lock class, so the following loop:
for (i = 0; i < ucsi->cap.num_connectors; i++) ucsi_register_port(connector[i]) mutex_lock(&connector[i]->lock)
looks like a recursive acquire of the same mutex. Put each connector lock into a dedicated lock class so that lockdep doesn't see it as a possible recursion.
Stable-dep-of: 7aa7d4bf9d3f ("usb: typec: ucsi: Fix race condition and ordering in port unregistration")
--- drivers/usb/typec/ucsi/ucsi.c | 8 ++++++++ drivers/usb/typec/ucsi/ucsi.h | 1 + 2 files changed, 9 insertions(+)
drivers/usb/typec/ucsi/ucsi.c +8 -0
@@ -1569,6 +1569,7 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)INIT_WORK (&con->work, ucsi_handle_connector_change);init_completion (&con->complete);mutex_init (&con->lock); +lockdep_set_class (&con->lock, &con->lock_key);INIT_LIST_HEAD (&con->partner_tasks); con->ucsi = ucsi;
@@ -1808,6 +1809,9 @@ static int ucsi_init(struct ucsi *ucsi) goto err_reset; } +for (i =0 ; i < ucsi->cap.num_connectors; i++) +lockdep_register_key (&connector[i].lock_key); +/* Register all connectors */ for (i =0 ; i < ucsi->cap.num_connectors; i++) { connector[i].num = i +1 ;
@@ -1837,6 +1841,9 @@ static int ucsi_init(struct ucsi *ucsi)return 0 ; err_unregister: +for (i =0 ; i < ucsi->cap.num_connectors; i++) +lockdep_unregister_key (&connector[i].lock_key); +for (con = connector; con->port; con++) {if (con->wq)destroy_workqueue (con->wq);
@@ -2107,6 +2114,7 @@ void ucsi_unregister(struct ucsi *ucsi)usb_power_delivery_unregister (ucsi->connector[i].pd); ucsi->connector[i].pd =NULL ;typec_unregister_port (ucsi->connector[i].port); +lockdep_unregister_key (&ucsi->connector[i].lock_key); }kfree (ucsi->connector);
drivers/usb/typec/ucsi/ucsi.h +1 -0
@@ -422,6 +422,7 @@ struct ucsi_connector {struct ucsi *ucsi;struct mutex lock;/* port lock */ +struct lock_class_key lock_key;struct work_struct work;struct completion complete;struct workqueue_struct *wq;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 usb: typec: ucsi: Fix race condition and ordering in port unregistration
From: Andrei Kuchynski <[email protected]>
[ Upstream commit 7aa7d4bf9d3fa9a6a47b640ad103ab433b7ff261 ]
A synchronization issue exists during port unregistration where pending partner work items can race against workqueue destruction, leading to use-after-free conditions:
cros_ec_ucsi cros_ec_ucsi.3.auto: error -ETIMEDOUT: PPM init failed
BUG: kernel NULL pointer dereference, address: 0000000000000000
RIP: 0010:__queue_work+0x83/0x4a0
Call Trace:
<IRQ>
__cfi_delayed_work_timer_fn+0x10/0x10
run_timer_softirq+0x3b6/0xbd0
sched_clock_cpu+0xc/0x110
irq_exit_rcu+0x18d/0x330
fred_sysvec_apic_timer_interrupt+0x5e/0x80Fix this by ensuring strict ordering and proper serialization during teardown:
1. Move ucsi_unregister_partner() to the beginning of the teardown sequence and protect it under the connector mutex lock.
2. Ensure all pending partner tasks are explicitly flushed and finished before the workqueue is destroyed.
3. Switch from mod_delayed_work() to a cancel_delayed_work() and queue_delayed_work() sequence. This guarantees that items currently marked as pending won't be scheduled an additional time, preventing a double release of resources which leads to the following crash:
Oops: general protection fault, probably for non-canonical address
0xdead000000000122: 0000 [#1] SMP NOPTI
Workqueue: cros_ec_ucsi.3.auto-con2 ucsi_poll_worker
RIP: 0010:ucsi_poll_worker+0x65/0x1e0
Call Trace:
<TASK>
process_scheduled_works+0x218/0x6d0
worker_thread+0x188/0x3f0
__cfi_worker_thread+0x10/0x10
kthread+0x226/0x2a0To ensure these rules are applied identically across both the normal teardown and the ucsi_init() error paths, consolidate the cleanup logic into a new helper, ucsi_unregister_port().
--- drivers/usb/typec/ucsi/ucsi.c | 82 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 43 deletions(-)
drivers/usb/typec/ucsi/ucsi.c +39 -43
@@ -1719,6 +1719,42 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)return ret; } +static void ucsi_unregister_port (struct ucsi_connector *con) +{ +struct ucsi_work *uwork; + +if (con->wq) { +mutex_lock (&con->lock); +ucsi_unregister_partner (con); +/* +* queue delayed items immediately so they can execute +* and free themselves before the wq is destroyed +*/ +list_for_each_entry (uwork, &con->partner_tasks, node) { +if (cancel_delayed_work (&uwork->work)) +queue_delayed_work (con->wq, &uwork->work,0 ); + } +mutex_unlock (&con->lock); + +destroy_workqueue (con->wq); + con->wq =NULL ; + }else { +ucsi_unregister_partner (con); + } + +ucsi_unregister_altmodes (con,UCSI_RECIPIENT_CON ); +ucsi_unregister_port_psy (con); + +usb_power_delivery_unregister_capabilities (con->port_sink_caps); + con->port_sink_caps =NULL ; +usb_power_delivery_unregister_capabilities (con->port_source_caps); + con->port_source_caps =NULL ; +usb_power_delivery_unregister (con->pd); + con->pd =NULL ; +typec_unregister_port (con->port); + con->port =NULL ; +} +static u64 ucsi_get_supported_notifications (struct ucsi *ucsi) {u16 features = ucsi->cap.features;
@@ -1844,22 +1880,8 @@ static int ucsi_init(struct ucsi *ucsi)for (i =0 ; i < ucsi->cap.num_connectors; i++)lockdep_unregister_key (&connector[i].lock_key); -for (con = connector; con->port; con++) { -if (con->wq) -destroy_workqueue (con->wq); -ucsi_unregister_partner (con); -ucsi_unregister_altmodes (con,UCSI_RECIPIENT_CON ); -ucsi_unregister_port_psy (con); - -usb_power_delivery_unregister_capabilities (con->port_sink_caps); - con->port_sink_caps =NULL ; -usb_power_delivery_unregister_capabilities (con->port_source_caps); - con->port_source_caps =NULL ; -usb_power_delivery_unregister (con->pd); - con->pd =NULL ; -typec_unregister_port (con->port); - con->port =NULL ; - } +for (con = connector; con->port; con++) +ucsi_unregister_port (con);kfree (connector); err_reset:memset (&ucsi->cap,0 ,sizeof (ucsi->cap));
@@ -2087,33 +2109,7 @@ void ucsi_unregister(struct ucsi *ucsi)for (i =0 ; i < ucsi->cap.num_connectors; i++) {cancel_work_sync (&ucsi->connector[i].work); - -if (ucsi->connector[i].wq) { -struct ucsi_work *uwork; - -mutex_lock (&ucsi->connector[i].lock); -/* -* queue delayed items immediately so they can execute -* and free themselves before the wq is destroyed -*/ -list_for_each_entry (uwork, &ucsi->connector[i].partner_tasks, node) -mod_delayed_work (ucsi->connector[i].wq, &uwork->work,0 ); -mutex_unlock (&ucsi->connector[i].lock); -destroy_workqueue (ucsi->connector[i].wq); - } - -ucsi_unregister_partner (&ucsi->connector[i]); -ucsi_unregister_altmodes (&ucsi->connector[i], -UCSI_RECIPIENT_CON ); -ucsi_unregister_port_psy (&ucsi->connector[i]); - -usb_power_delivery_unregister_capabilities (ucsi->connector[i].port_sink_caps); - ucsi->connector[i].port_sink_caps =NULL ; -usb_power_delivery_unregister_capabilities (ucsi->connector[i].port_source_caps); - ucsi->connector[i].port_source_caps =NULL ; -usb_power_delivery_unregister (ucsi->connector[i].pd); - ucsi->connector[i].pd =NULL ; -typec_unregister_port (ucsi->connector[i].port); +ucsi_unregister_port (&ucsi->connector[i]);lockdep_unregister_key (&ucsi->connector[i].lock_key); }
Signature · 1 line
2.53.0