From: Kean Ren <[email protected]>

When an eDP panel is still recovering right after resume (e.g. lid open
or AC return on a ThinkPad that has been in s2idle for hours), AUX/DPCD
writes may transiently fail. Two distinct code paths compound this into
a kernel WARN at dce_aux_transfer_raw():

  1. dpcd_set_link_settings() reuses a single `status` variable for
     four core_link_write_dpcd() calls and only emits DC_LOG_ERROR on
     failure. The first failing write gets overwritten by the next
     call, so the function can return DC_OK even when every DPCD write
     failed. Callers therefore cannot tell that the link is unhealthy
     and continue with PSR setup on a dead AUX channel.

  2. edp_setup_psr() does not consult link->link_status.link_active
     before pushing the PSR enable DPCD writes. When the link training
     failed, the sink is not actually there to ACK, so
     dm_helpers_dp_write_dpcd() -> dce_aux_transfer_raw() hangs until
     AUX_SW_DONE times out and triggers ASSERT_CRITICAL().

Observed on a Lenovo ThinkPad 21XHZDY2CN (BIOS R3HET22W 1.08) running
Ubuntu 24.04 with 6.17.0-1030-oem. The user-visible trigger is usually
a network event right after resume (unplug/replug the r8169 Ethernet
cable, NetworkManager roaming to wlan, or a lid-close -> lid-open
cycle). The dbus signal from those events causes a Wayland compositor
(gnome-shell) or an X11 client running under XWayland to issue
DRM_IOCTL_MODE_SETCRTC, which reaches amdgpu_dm_enable_self_refresh()
and then edp_setup_psr(). The "Xorg" comm name in the WARN trace is
XWayland, since this box boots into a GNOME Wayland session.

```
amdgpu 0000:c6:00.0: [drm] enabling link 0 failed: 15
amdgpu 0000:c6:00.0: [drm] *ERROR* dpcd_set_link_settings:1122: 
core_link_write_dpcd (DP_DOWNSPREAD_CTRL) failed
amdgpu 0000:c6:00.0: [drm] *ERROR* dpcd_set_link_settings:1127: 
core_link_write_dpcd (DP_LANE_COUNT_SET) failed
amdgpu 0000:c6:00.0: [drm] *ERROR* dpcd_set_link_settings:1144: 
core_link_write_dpcd (DP_LINK_BW_SET) failed
amdgpu 0000:c6:00.0: [drm] *ERROR* dpcd_set_link_settings:1149: 
core_link_write_dpcd (DP_LINK_RATE_SET) failed
[- cut here -]
WARNING: CPU: 0 PID: 2615 at 
drivers/gpu/drm/amd/amdgpu/../display/dc/dce/dce_aux.c:393 
dce_aux_transfer_raw+0x296/0x2e0 [amdgpu]
CPU: 0 UID: 1000 PID: 2615 Comm: Xorg Tainted: G           O        
6.17.0-1030-oem #30-Ubuntu PREEMPT(voluntary)
Tainted: [O]=OOT_MODULE
Hardware name: LENOVO 21XHZDY2CN/21XHZDY2CN, BIOS R3HET22W (1.08 ) 06/24/2026
RIP: 0010:dce_aux_transfer_raw+0x296/0x2e0 [amdgpu]
Code: ff e9 49 ff ff ff 41 c7 04 24 04 00 00 00 eb eb 3c 01 0f 87 4c f4 34 00 
83 e0 01 3c 01 19 c0 83 e0 c0 83 c0 50 e9 3f fe ff ff <0f> 0b 41 c7 04 24 03 00 
00 00 eb c5 41 c7 04 24 03 00 00 00 eb bb
RSP: 0018:ffffcdd6c53272f8 EFLAGS: 00010246
RAX: 0000000062000000 RBX: ffff8d353020fc80 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffcdd6c5327358 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: ffffcdd6c53273ac
R13: ffffcdd6c53273b0 R14: 0000000000000001 R15: ffff8d3562690000
FS:  00007b3cc831aac0(0000) GS:ffff8d4c6d669000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000700f940020f8 CR3: 000000012fe37000 CR4: 0000000000f50ef0
PKRU: 55555554
Call stack:
 <TASK>
 link_aux_transfer_raw+0x48/0x80 [amdgpu]
 dc_link_aux_transfer_raw+0x24/0x40 [amdgpu]
 dm_dp_aux_transfer+0xee/0x2b0 [amdgpu]
 drm_dp_dpcd_access+0xbe/0x160 [drm_display_helper]
 drm_dp_dpcd_write+0xc4/0x120 [drm_display_helper]
 dm_helpers_dp_write_dpcd+0x29/0x60 [amdgpu]
 edp_setup_psr+0x156/0x5a0 [amdgpu]
 dc_link_setup_psr+0x20/0x40 [amdgpu]
 amdgpu_dm_link_setup_psr+0x155/0x1a0 [amdgpu]
 ? dm_write_reg_func+0x47/0xc0 [amdgpu]
 amdgpu_dm_enable_self_refresh+0xaa/0x240 [amdgpu]
 amdgpu_dm_commit_planes+0x636/0x1740 [amdgpu]
 ? manage_dm_interrupts+0xa5/0x280 [amdgpu]
 amdgpu_dm_atomic_commit_tail+0xb04/0x1270 [amdgpu]
 ? __set_output_tf.constprop.0+0xfd/0x1a0 [amdgpu]
 commit_tail+0xc6/0x1b0
 drm_atomic_helper_commit+0x132/0x160
 drm_atomic_commit+0xac/0xf0
 ? __pfx___drm_printfn_info+0x10/0x10
 drm_atomic_helper_set_config+0x82/0xd0
 drm_mode_setcrtc+0x3ff/0x9e0
 ? rmapiMapWithSecInfo+0x230/0x2b0 [nvidia]
 ? __pfx_drm_mode_setcrtc+0x10/0x10
 drm_ioctl_kernel+0xb4/0x110
 drm_ioctl+0x2ec/0x5b0
 ? __pfx_drm_mode_setcrtc+0x10/0x10
 amdgpu_drm_ioctl+0x4b/0xa0 [amdgpu]
 __x64_sys_ioctl+0xa2/0x100
 x64_sys_call+0x1226/0x2680
 do_syscall_64+0x80/0x8b0
 ? check_heap_object+0x17f/0x1c0
 ? nvidia_unlocked_ioctl+0x175/0x9a0 [nvidia]
 ? __x64_sys_ioctl+0xbf/0x100
 ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
 ? do_syscall_64+0xb6/0x8b0
 ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
 ? do_syscall_64+0xb6/0x8b0
 ? arch_exit_to_user_mode_prepare.isra.0+0xd/0xe0
 ? do_syscall_64+0xb6/0x8b0
 entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7b3cc8724f1d
Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 
89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 
1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00
RSP: 002b:00007ffe65c05b80 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000617240d63e10 RCX: 00007b3cc8724f1d
RDX: 00007ffe65c05c10 RSI: 00000000c06864a2 RDI: 0000000000000010
RBP: 00007ffe65c05bd0 R08: 0000000000000000 R09: 0000617240c4b700
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffe65c05c10
R13: 00000000c06864a2 R14: 0000000000000010 R15: 000061723f332b30
 </TASK>
[- end trace 0000000000000000 -]
```

Fix both issues:

  - dpcd_set_link_settings(): use a separate `result` variable for each
    core_link_write_dpcd() call and return immediately on the first
    failure, so callers see the real AUX/DPCD state.

  - edp_setup_psr(): short-circuit when link->link_status.link_active
    is false, so we never push PSR configuration over a dead AUX
    channel.

Signed-off-by: Kean Ren <[email protected]>
---
 drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c       | 33 
++++++++++++++++----
 drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c | 11 
+++++++
 2 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c 
b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c
index 605bf19dc4f2..881cba95427f 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_dp_training.c
@@ -1097,6 +1097,7 @@ enum dc_status dpcd_set_link_settings(
 {
        uint8_t rate;
        enum dc_status status;
+       enum dc_status result;

        union down_spread_ctrl downspread = {0};
        union lane_count_set lane_count_set = {0};
@@ -1117,15 +1118,25 @@ enum dc_status dpcd_set_link_settings(
                                
link->dpcd_caps.max_ln_count.bits.POST_LT_ADJ_REQ_SUPPORTED;
        }

-       status = core_link_write_dpcd(link, DP_DOWNSPREAD_CTRL,
-               &downspread.raw, sizeof(downspread));
-       if (status != DC_OK)
+       /* Bail out on the first DPCD write failure so callers can react and
+        * subsequent operations (e.g. PSR setup) do not keep poking an
+        * unhealthy AUX channel. Without this, a transient AUX/HPD glitch
+        * during resume leads to a cascade of DPCD errors and ultimately a
+        * WARN at dce_aux_transfer_raw() because AUX_SW_DONE never asserts.
+        */
+       result = core_link_write_dpcd(link, DP_DOWNSPREAD_CTRL,
+                                     &downspread.raw, sizeof(downspread));
+       if (result != DC_OK) {
                DC_LOG_ERROR("%s:%d: core_link_write_dpcd (DP_DOWNSPREAD_CTRL) 
failed\n", __func__, __LINE__);
+               return result;
+       }

-       status = core_link_write_dpcd(link, DP_LANE_COUNT_SET,
-               &lane_count_set.raw, 1);
-       if (status != DC_OK)
+       result = core_link_write_dpcd(link, DP_LANE_COUNT_SET,
+                                     &lane_count_set.raw, 1);
+       if (result != DC_OK) {
                DC_LOG_ERROR("%s:%d: core_link_write_dpcd (DP_LANE_COUNT_SET) 
failed\n", __func__, __LINE__);
+               return result;
+       }

        if (link->dpcd_caps.dpcd_rev.raw >= DPCD_REV_13 &&
                        lt_settings->link_settings.use_link_rate_set == true) {
@@ -1140,20 +1151,26 @@ enum dc_status dpcd_set_link_settings(
                        core_link_read_dpcd(link, DP_SUPPORTED_LINK_RATES,
                                        supported_link_rates, 
sizeof(supported_link_rates));
                }
-               status = core_link_write_dpcd(link, DP_LINK_BW_SET, &rate, 1);
-               if (status != DC_OK)
+               result = core_link_write_dpcd(link, DP_LINK_BW_SET, &rate, 1);
+               if (result != DC_OK) {
                        DC_LOG_ERROR("%s:%d: core_link_write_dpcd 
(DP_LINK_BW_SET) failed\n", __func__, __LINE__);
+                       return result;
+               }

-               status = core_link_write_dpcd(link, DP_LINK_RATE_SET,
-                               &lt_settings->link_settings.link_rate_set, 1);
-               if (status != DC_OK)
+               result = core_link_write_dpcd(link, DP_LINK_RATE_SET,
+                                             
&lt_settings->link_settings.link_rate_set, 1);
+               if (result != DC_OK) {
                        DC_LOG_ERROR("%s:%d: core_link_write_dpcd 
(DP_LINK_RATE_SET) failed\n", __func__, __LINE__);
+                       return result;
+               }
        } else {
                rate = get_dpcd_link_rate(&lt_settings->link_settings);

-               status = core_link_write_dpcd(link, DP_LINK_BW_SET, &rate, 1);
-               if (status != DC_OK)
+               result = core_link_write_dpcd(link, DP_LINK_BW_SET, &rate, 1);
+               if (result != DC_OK) {
                        DC_LOG_ERROR("%s:%d: core_link_write_dpcd 
(DP_LINK_BW_SET) failed\n", __func__, __LINE__);
+                       return result;
+               }
        }

        if (rate) {
diff --git 
a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c 
b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
index 80a372ceaa51..43a0facc8884 100644
--- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
+++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c
@@ -699,6 +699,17 @@ bool edp_setup_psr(struct dc_link *link,
        if (!link)
                return false;

+       /* Skip PSR setup when the eDP link is not active. When AUX/DPCD
+        * writes are failing (e.g. after a resume where the panel has not
+        * fully come back yet), edp_setup_psr() will still try to push
+        * configuration over the AUX channel. That auxiliary transfer never
+        * completes and triggers ASSERT_CRITICAL() in dce_aux_transfer_raw().
+        * The DPCD read of the PSR cap below is also unsafe on a dead link,
+        * so bail out early before touching the sink.
+        */
+       if (!link->link_status.link_active)
+               return false;
+
        /* This is a workaround: some vendors require the source to
         * read the PSR cap; otherwise, the vendor's PSR feature will
         * fall back to its default behavior, causing a misconfiguration
--
2.47.3

Reply via email to