On Mon, Jul 13, 2026 at 4:19 PM Mario Limonciello
<[email protected]> wrote:
>
> 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 <[email protected]>
> Reported-by: [email protected]
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221073
> Signed-off-by: Mario Limonciello <[email protected]>

Acked-by: Alex Deucher <[email protected]>

> ---
>  drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c     | 12 +++++
>  drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h | 11 +++++
>  .../drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c  | 45 +++++++++++++++++++
>  3 files changed, 68 insertions(+)
>
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c 
> b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c
> index 4314dff4ac996..c1ceec7a1986b 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c
> +++ b/drivers/gpu/drm/amd/pm/swsmu/amdgpu_smu.c
> @@ -1366,6 +1366,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;
> @@ -1427,6 +1435,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;
>  }
>
> @@ -1449,6 +1459,8 @@ static int smu_sw_fini(struct amdgpu_ip_block *ip_block)
>
>         smu_fini_microcode(smu);
>
> +       smu_set_power_dep(smu, false);
> +
>         return 0;
>  }
>
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h 
> b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> index f8fd93999617d..7ea7c4a5279be 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> +++ b/drivers/gpu/drm/amd/pm/swsmu/inc/amdgpu_smu.h
> @@ -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;
> @@ -1618,6 +1621,14 @@ struct pptable_funcs {
>          */
>         int (*ras_send_msg)(struct smu_context *smu,
>                             enum smu_message_type msg, uint32_t param, 
> uint32_t *read_arg);
> +
> +       /**
> +        * @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 {
> diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c 
> b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c
> index 2fe006de927a3..2abdfef8644e0 100644
> --- a/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c
> +++ b/drivers/gpu/drm/amd/pm/swsmu/smu14/smu_v14_0_0_ppt.c
> @@ -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)
> --
> 2.43.0
>

Reply via email to