|
Oliver Neukum |
9ea489 |
From 62c73bfea048e66168df09da6d3e4510ecda40bb Mon Sep 17 00:00:00 2001
|
|
Oliver Neukum |
9ea489 |
From: Sven Peter <sven@svenpeter.dev>
|
|
Oliver Neukum |
9ea489 |
Date: Mon, 28 Nov 2022 17:15:26 +0100
|
|
Oliver Neukum |
9ea489 |
Subject: [PATCH] usb: dwc3: Fix race between dwc3_set_mode and __dwc3_set_mode
|
|
Oliver Neukum |
9ea489 |
Git-commit: 62c73bfea048e66168df09da6d3e4510ecda40bb
|
|
Oliver Neukum |
9ea489 |
References: git-fixes
|
|
Oliver Neukum |
9ea489 |
Patch-mainline: v6.2-rc1
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
dwc->desired_dr_role is changed by dwc3_set_mode inside a spinlock but
|
|
Oliver Neukum |
9ea489 |
then read by __dwc3_set_mode outside of that lock. This can lead to a
|
|
Oliver Neukum |
9ea489 |
race condition when very quick successive role switch events happen:
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
CPU A
|
|
Oliver Neukum |
9ea489 |
dwc3_set_mode(DWC3_GCTL_PRTCAP_HOST) // first role switch event
|
|
Oliver Neukum |
9ea489 |
spin_lock_irqsave(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
dwc->desired_dr_role = mode; // DWC3_GCTL_PRTCAP_HOST
|
|
Oliver Neukum |
9ea489 |
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
queue_work(system_freezable_wq, &dwc->drd_work);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
CPU B
|
|
Oliver Neukum |
9ea489 |
__dwc3_set_mode
|
|
Oliver Neukum |
9ea489 |
// ....
|
|
Oliver Neukum |
9ea489 |
spin_lock_irqsave(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
// desired_dr_role is DWC3_GCTL_PRTCAP_HOST
|
|
Oliver Neukum |
9ea489 |
dwc3_set_prtcap(dwc, dwc->desired_dr_role);
|
|
Oliver Neukum |
9ea489 |
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
CPU A
|
|
Oliver Neukum |
9ea489 |
dwc3_set_mode(DWC3_GCTL_PRTCAP_DEVICE) // second event
|
|
Oliver Neukum |
9ea489 |
spin_lock_irqsave(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
dwc->desired_dr_role = mode; // DWC3_GCTL_PRTCAP_DEVICE
|
|
Oliver Neukum |
9ea489 |
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
CPU B (continues running __dwc3_set_mode)
|
|
Oliver Neukum |
9ea489 |
switch (dwc->desired_dr_role) { // DWC3_GCTL_PRTCAP_DEVICE
|
|
Oliver Neukum |
9ea489 |
// ....
|
|
Oliver Neukum |
9ea489 |
case DWC3_GCTL_PRTCAP_DEVICE:
|
|
Oliver Neukum |
9ea489 |
// ....
|
|
Oliver Neukum |
9ea489 |
ret = dwc3_gadget_init(dwc);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
We then have DWC3_GCTL.DWC3_GCTL_PRTCAPDIR = DWC3_GCTL_PRTCAP_HOST and
|
|
Oliver Neukum |
9ea489 |
dwc->current_dr_role = DWC3_GCTL_PRTCAP_HOST but initialized the
|
|
Oliver Neukum |
9ea489 |
controller in device mode. It's also possible to get into a state
|
|
Oliver Neukum |
9ea489 |
where both host and device are intialized at the same time.
|
|
Oliver Neukum |
9ea489 |
Fix this race by creating a local copy of desired_dr_role inside
|
|
Oliver Neukum |
9ea489 |
__dwc3_set_mode while holding dwc->lock.
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
Fixes: 41ce1456e1db ("usb: dwc3: core: make dwc3_set_mode() work properly")
|
|
Oliver Neukum |
9ea489 |
Cc: stable <stable@kernel.org>
|
|
Oliver Neukum |
9ea489 |
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
|
|
Oliver Neukum |
9ea489 |
Signed-off-by: Sven Peter <sven@svenpeter.dev>
|
|
Oliver Neukum |
9ea489 |
Link: https://lore.kernel.org/r/20221128161526.79730-1-sven@svenpeter.dev
|
|
Oliver Neukum |
9ea489 |
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
Oliver Neukum |
9ea489 |
Signed-off-by: Oliver Neukum <oneukum@suse.com>
|
|
Oliver Neukum |
9ea489 |
---
|
|
Oliver Neukum |
9ea489 |
drivers/usb/dwc3/core.c | 17 +++++++++++------
|
|
Oliver Neukum |
9ea489 |
1 file changed, 11 insertions(+), 6 deletions(-)
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
--- a/drivers/usb/dwc3/core.c
|
|
Oliver Neukum |
9ea489 |
+++ b/drivers/usb/dwc3/core.c
|
|
Oliver Neukum |
9ea489 |
@@ -118,17 +118,22 @@ static void __dwc3_set_mode(struct work_
|
|
Oliver Neukum |
9ea489 |
struct dwc3 *dwc = work_to_dwc(work);
|
|
Oliver Neukum |
9ea489 |
unsigned long flags;
|
|
Oliver Neukum |
9ea489 |
int ret;
|
|
Oliver Neukum |
9ea489 |
+ u32 desired_dr_role;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- if (!dwc->desired_dr_role)
|
|
Oliver Neukum |
9ea489 |
+ spin_lock_irqsave(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
+ desired_dr_role = dwc->desired_dr_role;
|
|
Oliver Neukum |
9ea489 |
+ spin_unlock_irqrestore(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
+
|
|
Oliver Neukum |
9ea489 |
+ if (!desired_dr_role)
|
|
Oliver Neukum |
9ea489 |
return;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- if (dwc->desired_dr_role == dwc->current_dr_role)
|
|
Oliver Neukum |
9ea489 |
+ if (desired_dr_role == dwc->current_dr_role)
|
|
Oliver Neukum |
9ea489 |
return;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
if (dwc->dr_mode != USB_DR_MODE_OTG)
|
|
Oliver Neukum |
9ea489 |
return;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG)
|
|
Oliver Neukum |
9ea489 |
+ if (desired_dr_role == DWC3_GCTL_PRTCAP_OTG)
|
|
Oliver Neukum |
9ea489 |
return;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
switch (dwc->current_dr_role) {
|
|
Oliver Neukum |
9ea489 |
@@ -145,13 +150,13 @@ static void __dwc3_set_mode(struct work_
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
spin_lock_irqsave(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- dwc3_set_prtcap(dwc, dwc->desired_dr_role);
|
|
Oliver Neukum |
9ea489 |
+ dwc3_set_prtcap(dwc, desired_dr_role);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- dwc->current_dr_role = dwc->desired_dr_role;
|
|
Oliver Neukum |
9ea489 |
+ dwc->current_dr_role = desired_dr_role;
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
spin_unlock_irqrestore(&dwc->lock, flags);
|
|
Oliver Neukum |
9ea489 |
|
|
Oliver Neukum |
9ea489 |
- switch (dwc->desired_dr_role) {
|
|
Oliver Neukum |
9ea489 |
+ switch (desired_dr_role) {
|
|
Oliver Neukum |
9ea489 |
case DWC3_GCTL_PRTCAP_HOST:
|
|
Oliver Neukum |
9ea489 |
ret = dwc3_host_init(dwc);
|
|
Oliver Neukum |
9ea489 |
if (ret)
|