[RFC PATCH] usb: gadget: tegra-xudc: Unwind device-mode power failures
From: Pengpeng Hou
Date: Fri Aug 28 2026 - 07:44:00 EST
The Tegra XUDC role worker ignores runtime-PM, PHY power and PHY mode
failures while entering device mode, and sets current_device_mode even
after an incomplete transition.
Acquire runtime PM with pm_runtime_resume_and_get(), unwind each
completed PHY and pad operation on failure, and only publish
current_device_mode after all steps succeed. The worker logs the error.
This is an RFC because the asynchronous role request has already
completed and maintainers may prefer retry or desired-role state
handling beyond local rollback.
The issue was identified via static analysis and manually reviewed.
Assisted-by: LLM
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
drivers/usb/gadget/udc/tegra-xudc.c | 38 ++++++++++++++++++++++-------
1 file changed, 29 insertions(+), 9 deletions(-)
diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c
index e9d33be02866..41015e5dc089 100644
--- a/drivers/usb/gadget/udc/tegra-xudc.c
+++ b/drivers/usb/gadget/udc/tegra-xudc.c
@@ -696,28 +696,43 @@ static void tegra_xudc_restore_port_speed(struct tegra_xudc *xudc)
xudc_writel(xudc, val, SSPX_CORE_CNT72);
}
-static void tegra_xudc_device_mode_on(struct tegra_xudc *xudc)
+static int tegra_xudc_device_mode_on(struct tegra_xudc *xudc)
{
int err;
- pm_runtime_get_sync(xudc->dev);
+ err = pm_runtime_resume_and_get(xudc->dev);
+ if (err < 0)
+ return err;
tegra_phy_xusb_utmi_pad_power_on(xudc->curr_utmi_phy);
err = phy_power_on(xudc->curr_utmi_phy);
if (err < 0)
- dev_err(xudc->dev, "UTMI power on failed: %d\n", err);
+ goto power_down_pad;
err = phy_power_on(xudc->curr_usb3_phy);
if (err < 0)
- dev_err(xudc->dev, "USB3 PHY power on failed: %d\n", err);
+ goto power_off_utmi;
dev_dbg(xudc->dev, "device mode on\n");
- phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
- USB_ROLE_DEVICE);
+ err = phy_set_mode_ext(xudc->curr_utmi_phy, PHY_MODE_USB_OTG,
+ USB_ROLE_DEVICE);
+ if (err < 0)
+ goto power_off_usb3;
xudc->current_device_mode = true;
+
+ return 0;
+
+power_off_usb3:
+ phy_power_off(xudc->curr_usb3_phy);
+power_off_utmi:
+ phy_power_off(xudc->curr_utmi_phy);
+power_down_pad:
+ tegra_phy_xusb_utmi_pad_power_down(xudc->curr_utmi_phy);
+ pm_runtime_put(xudc->dev);
+ return err;
}
static void tegra_xudc_device_mode_off(struct tegra_xudc *xudc)
@@ -779,11 +794,16 @@ static void tegra_xudc_usb_role_sw_work(struct work_struct *work)
{
struct tegra_xudc *xudc = container_of(work, struct tegra_xudc,
usb_role_sw_work);
+ int err;
- if (xudc->device_mode)
- tegra_xudc_device_mode_on(xudc);
- else
+ if (xudc->device_mode) {
+ err = tegra_xudc_device_mode_on(xudc);
+
+ if (err)
+ dev_err(xudc->dev, "failed to enter device mode: %d\n", err);
+ } else {
tegra_xudc_device_mode_off(xudc);
+ }
}
static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc,
--
2.43.0