]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
drm/amd: Create a device link between APU display and XHCI devices
authorMario Limonciello <mario.limonciello@amd.com>
Mon, 13 Jul 2026 19:53:13 +0000 (14:53 -0500)
committerAlex Deucher <alexander.deucher@amd.com>
Fri, 17 Jul 2026 21:41:39 +0000 (17:41 -0400)
Some AMD APU multi-function devices expose an integrated USB xHCI
controller. In some circumstances (such as larger VRAM), the PM core
can resume can fail when the xHCI controller is resuming in parallel
with the GPU/display function.

On affected systems, the xHCI controller can complete pci_pm_resume
and start resuming USB devices while the GPU is still in its much
longer resume path. This race condition leads to USB device resume
failures followed by:

  xhci_hcd ...: xHCI host not responding to stop endpoint command
  xhci_hcd ...: HC died; cleaning up

Create a device link from any xHCI controller sharing the same PCIe
root port as the APU display function. The link uses DL_FLAG_STATELESS
and DL_FLAG_PM_RUNTIME to ensure the GPU completes its resume before
the xHCI controller begins resuming USB devices.

This device link is done specifically in amdgpu so that if the
platform firmware has been modified such that this issue doesn't happen
the version can be detected and the workaround skipped.

Suggested-by: Aaron Ma <aaron.ma@canonical.com>
Reported-by: mrh@frame.work
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221073
Acked-by: Alex Deucher <alexander.deucher@amd.com>
Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Tested-by: Alexander F <superveridical@gmail.com>
Tested-by: Francis DB <francisdb@gmail.com>
Link: https://patch.msgid.link/20260713195313.1739762-1-mario.limonciello@amd.com
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 07c93d7eeb0d990bc1b8e3b1eafa464bc9feee97)
Cc: stable@vger.kernel.org
drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c
drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c

index 762ec3cede96e6cf34b45e83dd4a32845c000932..3969a7670482005facb8a00294288b96a75db146 100644 (file)
@@ -1367,6 +1367,14 @@ static void smu_feature_cap_init(struct smu_context *smu)
        bitmap_zero(fea_cap->cap_map, SMU_FEATURE_CAP_ID__COUNT);
 }
 
+static int smu_set_power_dep(struct smu_context *smu, bool enable)
+{
+       if (!smu->ppt_funcs->set_power_dep)
+               return 0;
+
+       return smu->ppt_funcs->set_power_dep(smu, enable);
+}
+
 static int smu_sw_init(struct amdgpu_ip_block *ip_block)
 {
        struct amdgpu_device *adev = ip_block->adev;
@@ -1428,6 +1436,8 @@ static int smu_sw_init(struct amdgpu_ip_block *ip_block)
        if (!smu->ppt_funcs->get_fan_control_mode)
                smu->adev->pm.no_fan = true;
 
+       smu_set_power_dep(smu, true);
+
        return 0;
 }
 
@@ -1450,6 +1460,8 @@ static int smu_sw_fini(struct amdgpu_ip_block *ip_block)
 
        smu_fini_microcode(smu);
 
+       smu_set_power_dep(smu, false);
+
        return 0;
 }
 
index d76e0b005308f03c1f4d71e63da8319d509d1b8f..e3a89e9a9df4168f1c6609e411ba9ecc260fd956 100644 (file)
@@ -749,6 +749,9 @@ struct smu_context {
        bool pm_enabled;
        bool is_apu;
 
+       /* Power dependency link from an integrated xHCI controller to the GPU */
+       struct device_link              *usb_power_link;
+
        uint32_t smc_driver_if_version;
        uint32_t smc_fw_if_version;
        uint32_t smc_fw_version;
@@ -1648,12 +1651,19 @@ struct pptable_funcs {
        int (*ras_send_msg)(struct smu_context *smu,
                            enum smu_message_type msg, uint32_t param, uint32_t *read_arg);
 
-
        /**
         * @get_ras_smu_drv: Get RAS smu driver interface
         * Return: ras_smu_drv *
         */
        int (*get_ras_smu_drv)(struct smu_context *smu, const struct ras_smu_drv **ras_smu_drv);
+
+       /**
+        * @set_power_dep: Create or destroy a power dependency link
+        * from an integrated xHCI controller to the GPU so that the GPU is
+        * resumed before the USB controller during PM resume. @enable is true
+        * to create the link and false to tear it down.
+        */
+       int (*set_power_dep)(struct smu_context *smu, bool enable);
 };
 
 typedef enum {
index 75719c47a41e2096b0d3271ec4e1fd86583a6d2e..3d73f2050bbef09523984f4c23c20e5dad4bf91b 100644 (file)
@@ -1701,6 +1701,50 @@ static int smu_v14_0_0_restore_user_od_settings(struct smu_context *smu)
        return 0;
 }
 
+/*
+ * Link any xHCI controller sharing the GPU's PCIe root port as a consumer
+ * of the GPU so the GPU resumes first, avoiding an xHCI resume race.
+ */
+static int smu_v14_0_0_set_power_dep(struct smu_context *smu, bool enable)
+{
+       struct amdgpu_device *adev = smu->adev;
+       struct pci_dev *gpu_pdev = adev->pdev;
+       struct pci_dev *root_port, *usb_pdev = NULL;
+       struct device_link *link;
+
+       if (!enable) {
+               if (smu->usb_power_link) {
+                       device_link_del(smu->usb_power_link);
+                       smu->usb_power_link = NULL;
+               }
+               return 0;
+       }
+
+       root_port = pcie_find_root_port(gpu_pdev);
+       while ((usb_pdev = pci_get_class(PCI_CLASS_SERIAL_USB_XHCI, usb_pdev))) {
+               struct pci_dev *usb_root;
+
+               usb_root = pcie_find_root_port(usb_pdev);
+               if (usb_root != root_port)
+                       continue;
+
+               /* Create device link: USB (consumer) depends on GPU (supplier) */
+               link = device_link_add(&usb_pdev->dev, &gpu_pdev->dev,
+                                      DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME);
+               if (link) {
+                       smu->usb_power_link = link;
+                       drm_info(adev_to_drm(adev), "USB controller %s D0 power state depends on %s\n",
+                                pci_name(usb_pdev), pci_name(gpu_pdev));
+                       /* Only create one link for the first USB controller found */
+                       break;
+               }
+       }
+
+       pci_dev_put(usb_pdev);
+
+       return 0;
+}
+
 static const struct pptable_funcs smu_v14_0_0_ppt_funcs = {
        .check_fw_status = smu_v14_0_check_fw_status,
        .check_fw_version = smu_cmn_check_fw_version,
@@ -1734,6 +1778,7 @@ static const struct pptable_funcs smu_v14_0_0_ppt_funcs = {
        .dpm_set_umsch_mm_enable = smu_v14_0_0_set_umsch_mm_enable,
        .get_dpm_clock_table = smu_v14_0_common_get_dpm_table,
        .set_mall_enable = smu_v14_0_common_set_mall_enable,
+       .set_power_dep = smu_v14_0_0_set_power_dep,
 };
 
 static void smu_v14_0_0_init_msg_ctl(struct smu_context *smu)