* [PATCH] usb: chipidea: stop asynchronous work before destroying roles
@ 2026-11-12 9:25 Xu Yang
2026-11-12 9:50 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Xu Yang @ 2026-11-12 9:25 UTC (permalink / raw)
To: peter.chen, gregkh
Cc: alexander.shishkin, linux-usb, linux-kernel, jun.li, imx
From: Xu Yang <xu.yang_2@nxp.com>
Role teardown currently removes the gadget and host roles while OTG
interrupts, FSM processing and work items may still be active. Such
asynchronous processing can queue or execute role-switch operations
against partially destroyed roles. In particular, a gadget instance
may remain registered after the controller has been removed.
Disable power_lost_work before starting role teardown. During OTG
destruction, disable the IRQ and ci->work, clear canceled ID and
B-session-valid events, and remove the OTG FSM before destroying the
workqueue.
Finally, destroy the gadget and host roles only after the OTG machinery
has been stopped. This establishes a teardown order in which event
sources are quiesced before their target resources are released.
Fixes: cbec6bd55a45 ("usb: chipidea: move otg related things to otg file")
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
---
drivers/usb/chipidea/core.c | 5 +++--
drivers/usb/chipidea/otg.c | 17 +++++++++++++----
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 3461dd763a6e..eb7b9c0746d8 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -942,10 +942,11 @@ EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role);
static inline void ci_role_destroy(struct ci_hdrc *ci)
{
- ci_hdrc_gadget_destroy(ci);
- ci_hdrc_host_destroy(ci);
+ disable_work_sync(&ci->power_lost_work);
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
ci_hdrc_otg_destroy(ci);
+ ci_hdrc_gadget_destroy(ci);
+ ci_hdrc_host_destroy(ci);
}
static void ci_get_otg_capable(struct ci_hdrc *ci)
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 89a6a37815f0..8ac8969a45f0 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -272,12 +272,21 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
*/
void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
{
- if (ci->wq)
- destroy_workqueue(ci->wq);
+ disable_irq(ci->irq);
+ if (disable_work_sync(&ci->work)) {
+ ci->id_event = false;
+ ci->b_sess_valid_event = false;
+ /* Balance disable_irq_nosync() performed in ci_otg_queue_work() */
+ enable_irq(ci->irq);
+ }
+
+ if (ci_otg_is_fsm_mode(ci))
+ ci_hdrc_otg_fsm_remove(ci);
/* Disable all OTG irq and clear status */
hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
OTGSC_INT_STATUS_BITS);
- if (ci_otg_is_fsm_mode(ci))
- ci_hdrc_otg_fsm_remove(ci);
+ if (ci->wq)
+ destroy_workqueue(ci->wq);
+ enable_irq(ci->irq);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] usb: chipidea: stop asynchronous work before destroying roles
2026-11-12 9:25 [PATCH] usb: chipidea: stop asynchronous work before destroying roles Xu Yang
@ 2026-11-12 9:50 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-11-12 9:50 UTC (permalink / raw)
To: Xu Yang; +Cc: linux-usb, imx, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] usb: chipidea: stale OTG event flags and teardown race in
ci_hdrc_otg_destroy()
It also noticed 1 related pre-existing issue this patch does not
introduce:
- [Critical] usb: chipidea: uncanceled otg_fsm_hrtimer in
ci_hdrc_otg_destroy() [1]
--
commit b8def951ac8ca82d212a5f915b8e9c3017f3be77
Author: Xu Yang <xu.yang_2@nxp.com>
usb: chipidea: stop asynchronous work before destroying roles
This commit reorders role and OTG teardown so that asynchronous work items,
interrupts, and the OTG FSM are stopped before the gadget and host roles and
workqueue are destroyed.
> diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
> index fecc7d7e2f0d7..ec11056dddfaf 100644
> --- a/drivers/usb/chipidea/otg.c
> +++ b/drivers/usb/chipidea/otg.c
> @@ -264,12 +264,21 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
> */
> void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
> {
> - if (ci->wq)
> - destroy_workqueue(ci->wq);
> + disable_irq(ci->irq);
> + if (disable_work_sync(&ci->work)) {
> + ci->id_event = false;
> + ci->b_sess_valid_event = false;
> + /* Balance disable_irq_nosync() performed in ci_otg_queue_work() */
> + enable_irq(ci->irq);
> + }
> +
> + if (ci_otg_is_fsm_mode(ci))
> + ci_hdrc_otg_fsm_remove(ci);
[Severity: Critical]
This problem wasn't introduced by this patch, but can ci->otg_fsm_hrtimer
fire after teardown [1] because neither ci_hdrc_otg_fsm_remove() nor
ci_hdrc_otg_destroy() cancels it?
In OTG FSM mode, ci_otg_add_timer() arms ci->otg_fsm_hrtimer with
ci_otg_hrtimer_func(), while ci_hdrc_otg_fsm_remove() only removes the sysfs
group. If an armed timer expires during or after teardown,
ci_otg_hrtimer_func() dereferences the freed struct ci_hdrc and queues work on
the destroyed ci->wq workqueue, causing a use-after-free.
> /* Disable all OTG irq and clear status */
> hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
> OTGSC_INT_STATUS_BITS);
> - if (ci_otg_is_fsm_mode(ci))
> - ci_hdrc_otg_fsm_remove(ci);
> + if (ci->wq)
> + destroy_workqueue(ci->wq);
> + enable_irq(ci->irq);
> }
[Severity: Medium]
Can leaving ci->id_event or ci->b_sess_valid_event set while ci->work is
disabled cause an unhandled interrupt storm after enable_irq(ci->irq)?
ci_hdrc_otg_destroy() only clears ci->id_event and ci->b_sess_valid_event when
disable_work_sync(&ci->work) cancels a pending work item, and it disables
ci->work before hw_write_otgsc() clears cable->enabled. An extcon event
arriving via ci_cable_notifier() -> ci_irq() before hw_write_otgsc() runs
(which disable_irq(ci->irq) does not block), or an earlier OTG FSM transition
past OTG_STATE_A_IDLE in ci_otg_fsm_work(), leaves ci->id_event or
ci->b_sess_valid_event set with ci->work disabled:
drivers/usb/chipidea/core.c:ci_irq_handler() {
if (ci->is_otg) {
...
if (ci->id_event || ci->b_sess_valid_event) {
ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
}
/* Handle device/host interrupt */
if (ci->role != CI_ROLE_END)
ret = ci_role(ci)->irq(ci);
}
Once enable_irq(ci->irq) runs at the end of ci_hdrc_otg_destroy(), any host or
gadget interrupt during role teardown takes that early return in
ci_irq_handler(). Because ci->work is disabled, queue_work() in
ci_otg_queue_work() returns false and immediately calls enable_irq(ci->irq) in
hardirq context without ever calling ci_role(ci)->irq(ci) to clear the
hardware interrupt status.
Disabling OTG interrupts with hw_write_otgsc() before disable_work_sync() and
unconditionally clearing ci->id_event and ci->b_sess_valid_event after
disable_work_sync() would avoid this.
[1] https://sashiko.dev/#/bug/linux-8569f327-701f-4376-8f59-b4c911b172bb
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261112092556.2747348-1-xu.yang_2@oss.nxp.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-12 9:50 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 9:25 [PATCH] usb: chipidea: stop asynchronous work before destroying roles Xu Yang
2026-11-12 9:50 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox