From: Sasha Levin Date: Fri, 7 Aug 2026 11:44:41 +0000 (-0400) Subject: Fixes for all trees X-Git-Tag: v6.6.151~26^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eee689ac1573d2943a93b97d99c0e5fc80103626;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-6.1/bluetooth-6lowpan-fix-using-chan-conn-as-indication-.patch b/queue-6.1/bluetooth-6lowpan-fix-using-chan-conn-as-indication-.patch new file mode 100644 index 0000000000..64a33b990a --- /dev/null +++ b/queue-6.1/bluetooth-6lowpan-fix-using-chan-conn-as-indication-.patch @@ -0,0 +1,68 @@ +From 4f13e01c982b697fff9f8170d4b12aed23a12b94 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Fri, 12 Jun 2026 10:21:09 -0400 +Subject: Bluetooth: 6lowpan: Fix using chan->conn as indication to no remote + netdev + +From: Luiz Augusto von Dentz + +[ Upstream commit d38eaf611839b85ade3dd3db309dbc8aaaaf0095 ] + +b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding +conn ref") don't reset the chan->conn to NULL anymore making the bt# +netdev not be remove once the last l2cap_chan_del is removed. + +Instead of restoring the original behavior this remove the logic of +keeping the interface after the last channel is removed because it +never worked as intended and the l2cap_chan_del always detach its +l2cap_conn which results in always removing the channel anyway. + +Fixes: b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref") +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +--- + net/bluetooth/6lowpan.c | 18 +++--------------- + 1 file changed, 3 insertions(+), 15 deletions(-) + +diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c +index e2196cf4f6f70..723ead2c04f16 100644 +--- a/net/bluetooth/6lowpan.c ++++ b/net/bluetooth/6lowpan.c +@@ -781,20 +781,10 @@ static void chan_close_cb(struct l2cap_chan *chan) + struct lowpan_btle_dev *dev = NULL; + struct lowpan_peer *peer; + int err = -ENOENT; +- bool last = false, remove = true; ++ bool last = false; + + BT_DBG("chan %p conn %p", chan, chan->conn); + +- if (chan->conn && chan->conn->hcon) { +- if (!is_bt_6lowpan(chan->conn->hcon)) +- return; +- +- /* If conn is set, then the netdev is also there and we should +- * not remove it. +- */ +- remove = false; +- } +- + spin_lock(&devices_lock); + + list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) { +@@ -821,10 +811,8 @@ static void chan_close_cb(struct l2cap_chan *chan) + + ifdown(dev->netdev); + +- if (remove) { +- INIT_WORK(&entry->delete_netdev, delete_netdev); +- schedule_work(&entry->delete_netdev); +- } ++ INIT_WORK(&entry->delete_netdev, delete_netdev); ++ schedule_work(&entry->delete_netdev); + } else { + spin_unlock(&devices_lock); + } +-- +2.53.0 + diff --git a/queue-6.1/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holdin.patch b/queue-6.1/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holdin.patch new file mode 100644 index 0000000000..3c2c18fcb4 --- /dev/null +++ b/queue-6.1/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holdin.patch @@ -0,0 +1,252 @@ +From 7b33f1a57bd459f54a4488183a2eeea2fa39ed40 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Thu, 6 Aug 2026 00:52:14 +0000 +Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref + +From: Marco Elver + +[ Upstream commit b66774b48dd98f07254951f74ea6f513efe7ff8b ] + +l2cap_chan_timeout() runs asynchronously and accesses chan->conn. If +the connection is torn down while the timer is running or pending, +chan->conn can be freed, leading to a use-after-free when the timer +worker attempts to lock conn->lock: + +| BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline] +| BUG: KASAN: slab-use-after-free in atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] +| BUG: KASAN: slab-use-after-free in __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] +| BUG: KASAN: slab-use-after-free in mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 +| Write of size 8 at addr ffff8881298d9550 by task kworker/2:1/83 +| +| CPU: 2 UID: 0 PID: 83 Comm: kworker/2:1 Not tainted 7.1.0-rc6-next-20260601-dirty #6 PREEMPT(full) +| Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 +| Workqueue: events l2cap_chan_timeout +| Call Trace: +| +| instrument_atomic_read_write include/linux/instrumented.h:112 [inline] +| atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] +| __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] +| mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 +| l2cap_chan_timeout+0x5d/0x1b0 net/bluetooth/l2cap_core.c:422 +| process_one_work kernel/workqueue.c:3326 [inline] +| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 +| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 +| kthread+0x346/0x430 kernel/kthread.c:436 +| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 +| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 +| +| +| Allocated by task 320: +| l2cap_conn_add+0xa7/0x820 net/bluetooth/l2cap_core.c:7075 +| l2cap_connect_cfm+0xdb/0xd70 net/bluetooth/l2cap_core.c:7452 +| hci_connect_cfm include/net/bluetooth/hci_core.h:2139 [inline] +| hci_remote_features_evt+0x52f/0x9f0 net/bluetooth/hci_event.c:3760 +| hci_event_func net/bluetooth/hci_event.c:7796 [inline] +| hci_event_packet+0x561/0xa70 net/bluetooth/hci_event.c:7847 +| hci_rx_work+0x370/0x890 net/bluetooth/hci_core.c:4040 +| process_one_work kernel/workqueue.c:3326 [inline] +| process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 +| worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 +| kthread+0x346/0x430 kernel/kthread.c:436 +| ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 +| ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 +| +| Freed by task 322: +| hci_disconn_cfm include/net/bluetooth/hci_core.h:2154 [inline] +| hci_conn_hash_flush+0x101/0x1f0 net/bluetooth/hci_conn.c:2736 +| hci_dev_close_sync+0x889/0xde0 net/bluetooth/hci_sync.c:5405 +| hci_dev_do_close net/bluetooth/hci_core.c:502 [inline] +| hci_unregister_dev+0x1f7/0x370 net/bluetooth/hci_core.c:2679 +| vhci_release+0x12a/0x180 drivers/bluetooth/hci_vhci.c:690 +| __fput+0x369/0x890 fs/file_table.c:510 +| task_work_run+0x160/0x1d0 kernel/task_work.c:233 +| get_signal+0xf5b/0x1120 kernel/signal.c:2810 +| arch_do_signal_or_restart+0x4d/0x600 arch/x86/kernel/signal.c:337 +| __exit_to_user_mode_loop kernel/entry/common.c:64 [inline] +| exit_to_user_mode_loop+0x85/0x510 kernel/entry/common.c:98 +| do_syscall_64+0x263/0x3d0 arch/x86/entry/syscall_64.c:100 +| entry_SYSCALL_64_after_hwframe+0x77/0x7f +| +| The buggy address belongs to the object at ffff8881298d9400 +| which belongs to the cache kmalloc-512 of size 512 +| The buggy address is located 336 bytes inside of +| freed 512-byte region [ffff8881298d9400, ffff8881298d9600) + +Fix it by having chan->conn hold a reference to l2cap_conn (via +l2cap_conn_get) when the channel is added to the connection, and +releasing it in the channel destructor. This ensures the l2cap_conn +remains alive as long as the channel exists. + +A new FLAG_DEL channel flag is introduced to indicate that the channel +has been deleted from its connection. l2cap_chan_del() atomically sets +this flag using test_and_set_bit() instead of setting chan->conn to +NULL. All asynchronous workers (l2cap_chan_timeout, l2cap_ack_timeout, +l2cap_monitor_timeout, l2cap_retrans_timeout) and l2cap_chan_send() +check FLAG_DEL to determine whether the channel has been torn down, +rather than testing chan->conn for NULL. + +Fixes: 8c8e620467a7 ("Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()") +Cc: +Cc: Siwei Zhang +Cc: Luiz Augusto von Dentz +Assisted-by: Gemini:gemini-3.1-pro-preview +Reported-by: https://sashiko.dev/#/patchset/20260521021249.3258069-1-oss%40fourdim.xyz +Signed-off-by: Marco Elver +Signed-off-by: Luiz Augusto von Dentz +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Todd Kjos +Signed-off-by: Sasha Levin +--- + include/net/bluetooth/l2cap.h | 1 + + net/bluetooth/l2cap_core.c | 33 ++++++++++++++++++++------------- + 2 files changed, 21 insertions(+), 13 deletions(-) + +diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h +index 97ad02840530e..ba230c3cf4315 100644 +--- a/include/net/bluetooth/l2cap.h ++++ b/include/net/bluetooth/l2cap.h +@@ -743,6 +743,7 @@ enum { + FLAG_ECRED_CONN_REQ_SENT, + FLAG_PENDING_SECURITY, + FLAG_HOLD_HCI_CONN, ++ FLAG_DEL, + }; + + /* Lock nesting levels for L2CAP channels. We need these because lockdep +diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c +index b5603531a9d60..d1881f48cd11c 100644 +--- a/net/bluetooth/l2cap_core.c ++++ b/net/bluetooth/l2cap_core.c +@@ -415,7 +415,7 @@ static void l2cap_chan_timeout(struct work_struct *work) + + BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); + +- if (!conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_put(chan); + return; + } +@@ -426,6 +426,9 @@ static void l2cap_chan_timeout(struct work_struct *work) + */ + l2cap_chan_lock(chan); + ++ if (test_bit(FLAG_DEL, &chan->flags)) ++ goto unlock; ++ + if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG) + reason = ECONNREFUSED; + else if (chan->state == BT_CONNECT && +@@ -438,10 +441,11 @@ static void l2cap_chan_timeout(struct work_struct *work) + + chan->ops->close(chan); + ++unlock: + l2cap_chan_unlock(chan); +- l2cap_chan_put(chan); + + mutex_unlock(&conn->chan_lock); ++ l2cap_chan_put(chan); + } + + struct l2cap_chan *l2cap_chan_create(void) +@@ -494,6 +498,9 @@ static void l2cap_chan_destroy(struct kref *kref) + list_del(&chan->global_l); + write_unlock(&chan_list_lock); + ++ if (chan->conn) ++ l2cap_conn_put(chan->conn); ++ + kfree(chan); + } + +@@ -625,7 +632,7 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) + + conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; + +- chan->conn = conn; ++ chan->conn = l2cap_conn_get(conn); + + switch (chan->chan_type) { + case L2CAP_CHAN_CONN_ORIENTED: +@@ -680,30 +687,26 @@ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) + + void l2cap_chan_del(struct l2cap_chan *chan, int err) + { +- struct l2cap_conn *conn = chan->conn; +- + __clear_chan_timer(chan); + +- BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err, ++ BT_DBG("chan %p, err %d, state %s", chan, err, + state_to_string(chan->state)); + + chan->ops->teardown(chan, err); + +- if (conn) { ++ if (!test_and_set_bit(FLAG_DEL, &chan->flags)) { + /* Delete from channel list */ + list_del(&chan->list); + + l2cap_chan_put(chan); + +- chan->conn = NULL; +- + /* Reference was only held for non-fixed channels or + * fixed channels that explicitly requested it using the + * FLAG_HOLD_HCI_CONN flag. + */ + if (chan->chan_type != L2CAP_CHAN_FIXED || + test_bit(FLAG_HOLD_HCI_CONN, &chan->flags)) +- hci_conn_drop(conn->hcon); ++ hci_conn_drop(chan->conn->hcon); + } + + if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state)) +@@ -1930,7 +1933,7 @@ static void l2cap_monitor_timeout(struct work_struct *work) + + l2cap_chan_lock(chan); + +- if (!chan->conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + return; +@@ -1951,7 +1954,7 @@ static void l2cap_retrans_timeout(struct work_struct *work) + + l2cap_chan_lock(chan); + +- if (!chan->conn) { ++ if (test_bit(FLAG_DEL, &chan->flags)) { + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + return; +@@ -2566,7 +2569,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len) + int err; + struct sk_buff_head seg_queue; + +- if (!chan->conn) ++ if (test_bit(FLAG_DEL, &chan->flags)) + return -ENOTCONN; + + /* Connectionless channel */ +@@ -3167,12 +3170,16 @@ static void l2cap_ack_timeout(struct work_struct *work) + + l2cap_chan_lock(chan); + ++ if (test_bit(FLAG_DEL, &chan->flags)) ++ goto unlock; ++ + frames_to_ack = __seq_offset(chan, chan->buffer_seq, + chan->last_acked_seq); + + if (frames_to_ack) + l2cap_send_rr_or_rnr(chan, 0); + ++unlock: + l2cap_chan_unlock(chan); + l2cap_chan_put(chan); + } +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index f881727b93..b8bd8cbfe5 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -480,3 +480,5 @@ mm-huge_memory-unlock-i_mmap_rwsem-before-releasing-.patch net-openvswitch-fix-skb-leak-on-flow-key-update-fail.patch firmware-stratix10-svc-fix-memory-leaks-and-list-cor.patch gpio-pch-use-raw_spinlock_t-for-the-register-lock.patch +bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holdin.patch +bluetooth-6lowpan-fix-using-chan-conn-as-indication-.patch diff --git a/queue-6.18/drm-amd-display-check-grph_flip-status-before-sendin.patch b/queue-6.18/drm-amd-display-check-grph_flip-status-before-sendin.patch new file mode 100644 index 0000000000..3d26a0ad30 --- /dev/null +++ b/queue-6.18/drm-amd-display-check-grph_flip-status-before-sendin.patch @@ -0,0 +1,397 @@ +From 5495b06776985b09d94ff2a2a0992ed1c15c527f Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 13:57:48 -0400 +Subject: drm/amd/display: check GRPH_FLIP status before sending event + +From: Leo Li + +[ Upstream commit 48ab86360af117123eb1b15e38f068acf3826400 ] + +[Why] + +After unifying DCN interrupt sources under VUPDATE_NO_LOCK, we have two +remaining issues to clean up: + +1. On DCN, flip completion is now delivered from VUPDATE_NO_LOCK + (dm_crtc_high_irq_handler) instead of GRPH_PFLIP. But VUPDATE_NO_LOCK + fires every frame, regardless of whether a flip has latched. + +2. There is a window during commit where a flip is armed (pflip_status = + SUBMITTED) but not yet programmed into HW. If the VUPDATE_NO_LOCK + fires in that window, its handler would deliver a flip event to + userspace before HW has latched to it. If userspace then renders to + what it believes is now the back buffer (but HW is still latched to + it!), it will cause display corruption. This issue seemed to have + been introduced by: + commit 1159898a88db ("drm/amd/display: Handle commit plane with no FB.") + Enabling replay or psr extended the duration of this window, and + hence made corruption more likely to be observed. + +[How] + +* Move acrtc->event/pflip_status arming to after + update_planes_and_stream_adapter() has programmed the flip into HW. + This closes the window where pflip_status is SUBMITTED but the flip is + not yet programmed. + +* Add dc_get_flip_pending_on_otg(), which reads the HUBP flip-pending + status straight from HW for the pipe(s) bound to an OTG instance. It + is keyed only by otg_inst and does not take or mutate a + dc_plane_state, so it is safe to call from the OTG interrupt handler + without racing a concurrent commit that may be modifying plane state. + +* Optimistically query for flip-pending after programming, in the event + that HW latched to the new fb between programming start and arming + event. If it latched, send the vblank event immediately, rather than + wait for the next vblank IRQ. + +* In the VUPDATE_NO_LOCK handler, only deliver flip completion once + dc_get_flip_pending_on_otg() reports the flip is no longer pending. + Otherwise leave the flip armed and retry on the next vupdate. + +* For DCE, maintain the existing behavior of arming flips before + programming, and relying on GRPH_FLIP to fire at HW latch. + +v2: +* Drop flip_programmed completion object, instead move + event/pflip_status arming after programming. +* For DCN, optimistically query for flip pending immediately after + programming, and if it latched, send event right away. + +v3: +* Fix event timestamps on optimistic flip latch detection, where it's + possible for it to run *before* the vupdate IRQ updates the timestamp. +* Add more docstrings for DCN vblank handling. +* Clean up if conditions in dm_arm_vblank_event(). +* Code style cleanup on braces surrounding multi-line statements. + +Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 +Assisted-by: Copilot:claude-opus-4.8 +Tested-by: Mario Limonciello (AMD) +Reviewed-by: Mario Limonciello (AMD) +Signed-off-by: Leo Li +Signed-off-by: Alex Deucher +(cherry picked from commit f64a9be5653689ff43e148cd8a6483077488c8e5) +Cc: stable@vger.kernel.org # 8382cd234981: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 191 ++++++++++++++---- + drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++++ + drivers/gpu/drm/amd/display/dc/dc.h | 1 + + 3 files changed, 197 insertions(+), 40 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index 99759559cc0f6..be267f5b9f8cc 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -676,13 +676,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, + * Deliver pageflip completion events (DCN only). + * + * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch +- * point. Deliver any pending pageflip completion event from here. ++ * point. Deliver any pending pageflip completion event from here, ++ * once HW has consumed the new address (the OTG no longer reports a ++ * pending flip). + * +- * NOTE: This can deliver an event for a flip that was armed but not yet +- * programmed into HW; that race is closed in a follow-up change by +- * checking the programmed flip status. ++ * Also handle the case here where there aren't any active planes and ++ * DCN HUBP may be clock-gated, so the flip-pending status may be ++ * undefined. + */ +- if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { ++ if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ acrtc->event) { ++ ++ if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { ++ drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); ++ acrtc->event = NULL; ++ drm_crtc_vblank_put(&acrtc->base); ++ acrtc->pflip_status = AMDGPU_FLIP_NONE; ++ } ++ /* ++ * If the flip is still pending, leave it armed and ++ * retry on the next vupdate. ++ */ ++ } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ acrtc->dm_irq_params.active_planes == 0) { ++ + if (acrtc->event) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; +@@ -9664,6 +9681,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_crtc *acrtc_attach, + } + } + ++static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, ++ struct dm_crtc_state *acrtc_state, ++ bool pflip_update, ++ bool cursor_update) ++{ ++ assert_spin_locked(&acrtc->base.dev->event_lock); ++ ++ if (!acrtc->base.state->event || acrtc_state->active_planes == 0) ++ return; ++ ++ if (pflip_update) { ++ drm_crtc_vblank_get(&acrtc->base); ++ WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); ++ /* Arm flip completion handling and event delivery after programming. */ ++ prepare_flip_isr(acrtc); ++ } else if (cursor_update) { ++ drm_crtc_vblank_get(&acrtc->base); ++ acrtc->event = acrtc->base.state->event; ++ acrtc->base.state->event = NULL; ++ } ++} ++ + static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + struct drm_device *dev, + struct amdgpu_display_manager *dm, +@@ -9687,6 +9726,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + bool cursor_update = false; + bool pflip_present = false; + bool immediate_flip = false; ++ bool flip_latched_during_prog = false; + bool dirty_rects_changed = false; + bool updated_planes_and_streams = false; + struct { +@@ -9922,39 +9962,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + usleep_range(1000, 1100); + } + +- /** +- * Prepare the flip event for the pageflip interrupt to handle. +- * +- * This only works in the case where we've already turned on the +- * appropriate hardware blocks (eg. HUBP) so in the transition case +- * from 0 -> n planes we have to skip a hardware generated event +- * and rely on sending it from software. +- */ +- if (acrtc_attach->base.state->event && +- acrtc_state->active_planes > 0) { +- drm_crtc_vblank_get(pcrtc); +- +- spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- +- WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE); +- prepare_flip_isr(acrtc_attach); +- +- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); +- } +- + if (acrtc_state->stream) { + if (acrtc_state->freesync_vrr_info_changed) + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; + } +- } else if (cursor_update && acrtc_state->active_planes > 0) { +- spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- if (acrtc_attach->base.state->event) { +- drm_crtc_vblank_get(pcrtc); +- acrtc_attach->event = acrtc_attach->base.state->event; +- acrtc_attach->base.state->event = NULL; ++ } ++ ++ /* ++ * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for ++ * event delivery. Only GRPH_FLIP handler can send pflip events, and it ++ * only fires if HW latched to the flip. Maintain legacy behavior by ++ * arming event before programming. ++ */ ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { ++ scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ dm_arm_vblank_event(acrtc_attach, acrtc_state, ++ pflip_present, cursor_update); + } +- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); + } + + /* Update the planes if changed or disable if we don't have any. */ +@@ -10056,17 +10081,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + amdgpu_dm_commit_cursors(state); + + /* +- * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. +- * However, an immediate (tearing / async) flip is latched by HW right +- * away and does not wait for the next vupdate, so deliver its +- * completion event here after programming. ++ * DCN specific vblank handling ++ * ============================ ++ * ++ * With the event_lock held, arm the vblank event, and determine whether ++ * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch ++ * point) handler. Do this *after* programming so that the IRQ handler ++ * will not deliver the event before HW laches onto the programmed ++ * values: ++ * ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * arm_vblank_event() ++ * vupdate() ++ * vupdate_handler() ++ * cook_timestamp() ++ * # prev flip already latched, ++ * # so flip_latched == true. ++ * if event_armed && flip_latched: ++ * send_vblank_event() ++ * # sent before latch, **BAD!** ++ * hw_program() ++ * vupdate() ++ * **latch** ++ * ++ * There's a consequence of arming after: it's possible for HW to latch ++ * between start of HW programming and acrtc->event/pflip_status arming. ++ * When this happens, the IRQ handler will send the event on the next ++ * immediate latch point, even though HW has already latched. This is ++ * handled by optimistically checking for HW latch after programming, ++ * and if latched, send the event immediately: + * +- * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so +- * this is DCN-only. ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * hw_program() ++ * vupdate() ++ * **latch** ++ * vupdate_handler() ++ * cook_timestamp() ++ * # event_armed == false ++ * # **no event sent!** ++ * arm_vblank_event() ++ * if flip_latched: ++ * **send_vblank_event()** ++ * disarm_vblank_event() ++ * ++ * The IRQ handler is expected to cook the timestamp, but we need to ++ * cook the timestamp before optimistic sending as well. That's because ++ * the following sequence is possible: ++ * ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * hw_program() ++ * arm_vblank_event() ++ * vupdate() ++ * **latch** ++ * if flip_latched: ++ * # Need cook before send! ++ * **cook_timestamp()** ++ * send_vblank_event() ++ * disarm_vblank_event() ++ * vupdate_handler() ++ * cook_timestamp() ++ * # event_armed == false ++ * # no event sent! ++ * ++ * Cooking twice is OK, since DRM scanout accurate timestamps report A) ++ * the previous vactive start if currently in vactive, or B) the next ++ * vactive start if currently in vblank (see &get_vblank_counter). 'A)' ++ * is what we want for the optimistic send, and for 'B)', we'll cook a ++ * timestamp no later than the next IRQ handler run. ++ * ++ * The more correct fix is to wrap programming and arming with the ++ * event_lock and thus serializing it with the IRQ handler. However, ++ * there are various sleep-waits within ++ * update_planes_and_stream_adapter() that makes spin locking illegal. ++ * And on full updates, it can take 1-2 frame-times to return (see ++ * commit_planes_for_stream). ++ * ++ * On DCE, GRPH_PFLIP IRQ is used and takes care of this. + */ +- if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ ++ if (updated_planes_and_streams) { ++ flip_latched_during_prog = ++ !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst); ++ } ++ ++ dm_arm_vblank_event(acrtc_attach, acrtc_state, ++ pflip_present, cursor_update); ++ ++ /* ++ * Deliver the event immediately on immediate flip, or on a ++ * update that has already latched. ++ */ ++ if ((immediate_flip || flip_latched_during_prog) && ++ acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc_attach->event) { + drm_crtc_accurate_vblank_count(&acrtc_attach->base); + drm_crtc_send_vblank_event(&acrtc_attach->base, +diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c +index 0347174173f34..927837249479f 100644 +--- a/drivers/gpu/drm/amd/display/dc/core/dc.c ++++ b/drivers/gpu/drm/amd/display/dc/core/dc.c +@@ -5427,6 +5427,51 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) + dal_irq_service_ack(dc->res_pool->irqs, src); + } + ++/* ++ * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG ++ * ++ * @dc: display core context @otg_inst: OTG instance to query ++ * ++ * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst, ++ * returning true if any of them has not yet latched its programmed surface ++ * address. ++ * ++ * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state, ++ * so it is safe to call from interrupt context without racing a concurrent ++ * commit that may be updating plane state. ++ * ++ * Return: true if a flip is still pending on the OTG, false otherwise. ++ */ ++bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst) ++{ ++ bool flip_pending = false; ++ int i; ++ ++ if (!dc || !dc->current_state) ++ return false; ++ ++ dc_exit_ips_for_hw_access(dc); ++ ++ for (i = 0; i < dc->res_pool->pipe_count; i++) { ++ struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; ++ struct hubp *hubp = pipe_ctx->plane_res.hubp; ++ ++ if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg) ++ continue; ++ ++ if (pipe_ctx->stream_res.tg->inst != otg_inst) ++ continue; ++ ++ if (hubp && hubp->funcs->hubp_is_flip_pending && ++ hubp->funcs->hubp_is_flip_pending(hubp)) { ++ flip_pending = true; ++ break; ++ } ++ } ++ ++ return flip_pending; ++} ++ + void dc_power_down_on_boot(struct dc *dc) + { + if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && +diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h +index 98f0b6b3c2136..390f948dad393 100644 +--- a/drivers/gpu/drm/amd/display/dc/dc.h ++++ b/drivers/gpu/drm/amd/display/dc/dc.h +@@ -2611,6 +2611,7 @@ enum dc_irq_source dc_interrupt_to_irq_source( + uint32_t ext_id); + bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable); + void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src); ++bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst); + enum dc_irq_source dc_get_hpd_irq_source_at_index( + struct dc *dc, uint32_t link_index); + +-- +2.53.0 + diff --git a/queue-6.18/drm-amd-display-exit-idle-optimizations-before-progr.patch b/queue-6.18/drm-amd-display-exit-idle-optimizations-before-progr.patch new file mode 100644 index 0000000000..18ac5b96da --- /dev/null +++ b/queue-6.18/drm-amd-display-exit-idle-optimizations-before-progr.patch @@ -0,0 +1,167 @@ +From c257a7c06ea915c5f661704b6a4777fdb7886b99 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 12:33:14 -0400 +Subject: drm/amd/display: Exit idle optimizations before programming + +From: Leo Li + +[ Upstream commit 8419331e64d92a8de5fc4feef0e305f201fb8b33 ] + +[Why] + +We need to exit PSR/IPS before programming. Before calling DC for +programming in amdgpu_dm_commit_planes(), there's a +vblank_control_workqueue flush. This waits for IPS and PSR exit. (See +drm_vblank_on/off() > amdgpu_dm_crtc_set_vblank() --queue_work()-> +amdgpu_dm_crtc_vblank_control_worker()) + +Prior to the tagged "Fixes:" change, drm_vblank_get() was called before +the workqueue flush. This ordering ensures that PSR exit occurred before +programming. After the "Fixes:" change, drm_vblank_get() is called after +the workqueue flush, leading to programming while idle optimizations are +still active. This can lead to incorrect flip_pending detection used by +vblank event delivery. + +[How] + +Split the vblank_get() component of `dm_arm_vblank_event()` into +`dm_arm_vblank_event_pre_programming()`, which is called before +programming. Call it before the vblank_control_workqueue flush. + +Includes a drive-by cleanup of prepare_flip_isr(): the only caller is +dm_arm_vblank_event() and it's simple enough to roll-in. + +v2: Fix checkpatch formatting warning on + drm_arm_vblank_event_pre_programming() arg alignment. + +Fixes: 48ab86360af1 ("drm/amd/display: check GRPH_FLIP status before sending event") +Cc: stable@vger.kernel.org +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141#note_3583205 +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/5527 +Assisted-by: Codex:gpt-5.6-sol +Assisted-by: Claude:opus-5 +Suggested-by: David Weber +Signed-off-by: Leo Li +Reviewed-by: Mario Limonciello (AMD) +Signed-off-by: Alex Deucher +(cherry picked from commit 05984e29520a28c27f5a2388742c957a6a87ee7a) +(cherry picked from commit 8419331e64d92a8de5fc4feef0e305f201fb8b33) +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 78 +++++++++++-------- + 1 file changed, 46 insertions(+), 32 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index be267f5b9f8cc..4f53297786623 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -9326,25 +9326,6 @@ static void remove_stream(struct amdgpu_device *adev, + acrtc->enabled = false; + } + +-static void prepare_flip_isr(struct amdgpu_crtc *acrtc) +-{ +- +- assert_spin_locked(&acrtc->base.dev->event_lock); +- WARN_ON(acrtc->event); +- +- acrtc->event = acrtc->base.state->event; +- +- /* Set the flip status */ +- acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; +- +- /* Mark this event as consumed */ +- acrtc->base.state->event = NULL; +- +- drm_dbg_state(acrtc->base.dev, +- "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", +- acrtc->crtc_id); +-} +- + static void update_freesync_state_on_stream( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, +@@ -9692,17 +9673,47 @@ static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, + return; + + if (pflip_update) { +- drm_crtc_vblank_get(&acrtc->base); + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); +- /* Arm flip completion handling and event delivery after programming. */ +- prepare_flip_isr(acrtc); ++ WARN_ON(acrtc->event); ++ ++ acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; ++ acrtc->event = acrtc->base.state->event; ++ acrtc->base.state->event = NULL; ++ ++ drm_dbg_state(acrtc->base.dev, ++ "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", ++ acrtc->crtc_id); + } else if (cursor_update) { +- drm_crtc_vblank_get(&acrtc->base); + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } + } + ++/** ++ * dm_arm_vblank_event_pre_programming - Prepare for programming ++ * @acrtc: The amdgpu CRTC to prepare ++ * @acrtc_state: The new CRTC state ++ * @pflip_update: Whether a page flip is being programmed ++ * @cursor_update: Whether a cursor update is being programmed ++ * ++ * Grab a reference on the vblank counter if a page flip or cursor update is to ++ * be programmed. Do this before programming so the HW is not in any ++ * idle-optimized state (such as PSR). ++ */ ++static void dm_arm_vblank_event_pre_programming(struct amdgpu_crtc *acrtc, ++ struct dm_crtc_state *acrtc_state, ++ bool pflip_update, ++ bool cursor_update) ++{ ++ assert_spin_locked(&acrtc->base.dev->event_lock); ++ ++ if (!acrtc->base.state->event || acrtc_state->active_planes == 0) ++ return; ++ ++ if (pflip_update || cursor_update) ++ drm_crtc_vblank_get(&acrtc->base); ++} ++ + static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + struct drm_device *dev, + struct amdgpu_display_manager *dm, +@@ -9969,16 +9980,19 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + } + } + +- /* +- * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for +- * event delivery. Only GRPH_FLIP handler can send pflip events, and it +- * only fires if HW latched to the flip. Maintain legacy behavior by +- * arming event before programming. +- */ +- if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { +- scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ dm_arm_vblank_event_pre_programming(acrtc_attach, acrtc_state, ++ pflip_present, ++ cursor_update); ++ /* ++ * DCE depends on a combination of GRPH_FLIP, VLINE0, and ++ * VUPDATE for event delivery. Only GRPH_FLIP handler can send ++ * pflip events, and it only fires if HW latched to the flip. ++ * Maintain legacy behavior by arming event before programming. ++ */ ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { + dm_arm_vblank_event(acrtc_attach, acrtc_state, +- pflip_present, cursor_update); ++ pflip_present, cursor_update); + } + } + +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index 57cb4f2302..ba44432ed9 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -362,3 +362,5 @@ net-handshake-take-a-long-lived-file-reference-at-su.patch net-handshake-hand-off-the-pinned-file-reference-to-.patch net-handshake-close-the-submit-side-sock_hold-race.patch net-handshake-drain-pending-requests-at-net-namespac.patch +drm-amd-display-check-grph_flip-status-before-sendin.patch +drm-amd-display-exit-idle-optimizations-before-progr.patch diff --git a/queue-7.1/drm-amd-display-check-grph_flip-status-before-sendin.patch b/queue-7.1/drm-amd-display-check-grph_flip-status-before-sendin.patch new file mode 100644 index 0000000000..c657fbbace --- /dev/null +++ b/queue-7.1/drm-amd-display-check-grph_flip-status-before-sendin.patch @@ -0,0 +1,397 @@ +From 03feabd751443ed9e2102ae995226dae041ddb15 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 29 Jul 2026 13:37:43 -0400 +Subject: drm/amd/display: check GRPH_FLIP status before sending event + +From: Leo Li + +[ Upstream commit 48ab86360af117123eb1b15e38f068acf3826400 ] + +[Why] + +After unifying DCN interrupt sources under VUPDATE_NO_LOCK, we have two +remaining issues to clean up: + +1. On DCN, flip completion is now delivered from VUPDATE_NO_LOCK + (dm_crtc_high_irq_handler) instead of GRPH_PFLIP. But VUPDATE_NO_LOCK + fires every frame, regardless of whether a flip has latched. + +2. There is a window during commit where a flip is armed (pflip_status = + SUBMITTED) but not yet programmed into HW. If the VUPDATE_NO_LOCK + fires in that window, its handler would deliver a flip event to + userspace before HW has latched to it. If userspace then renders to + what it believes is now the back buffer (but HW is still latched to + it!), it will cause display corruption. This issue seemed to have + been introduced by: + commit 1159898a88db ("drm/amd/display: Handle commit plane with no FB.") + Enabling replay or psr extended the duration of this window, and + hence made corruption more likely to be observed. + +[How] + +* Move acrtc->event/pflip_status arming to after + update_planes_and_stream_adapter() has programmed the flip into HW. + This closes the window where pflip_status is SUBMITTED but the flip is + not yet programmed. + +* Add dc_get_flip_pending_on_otg(), which reads the HUBP flip-pending + status straight from HW for the pipe(s) bound to an OTG instance. It + is keyed only by otg_inst and does not take or mutate a + dc_plane_state, so it is safe to call from the OTG interrupt handler + without racing a concurrent commit that may be modifying plane state. + +* Optimistically query for flip-pending after programming, in the event + that HW latched to the new fb between programming start and arming + event. If it latched, send the vblank event immediately, rather than + wait for the next vblank IRQ. + +* In the VUPDATE_NO_LOCK handler, only deliver flip completion once + dc_get_flip_pending_on_otg() reports the flip is no longer pending. + Otherwise leave the flip armed and retry on the next vupdate. + +* For DCE, maintain the existing behavior of arming flips before + programming, and relying on GRPH_FLIP to fire at HW latch. + +v2: +* Drop flip_programmed completion object, instead move + event/pflip_status arming after programming. +* For DCN, optimistically query for flip pending immediately after + programming, and if it latched, send event right away. + +v3: +* Fix event timestamps on optimistic flip latch detection, where it's + possible for it to run *before* the vupdate IRQ updates the timestamp. +* Add more docstrings for DCN vblank handling. +* Clean up if conditions in dm_arm_vblank_event(). +* Code style cleanup on braces surrounding multi-line statements. + +Fixes: 9b47278cec98 ("drm/amd/display: temp w/a for dGPU to enter idle optimizations") +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/3787 +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141 +Assisted-by: Copilot:claude-opus-4.8 +Tested-by: Mario Limonciello (AMD) +Reviewed-by: Mario Limonciello (AMD) +Signed-off-by: Leo Li +Signed-off-by: Alex Deucher +(cherry picked from commit f64a9be5653689ff43e148cd8a6483077488c8e5) +Cc: stable@vger.kernel.org # 8382cd234981: drm/amd/display: consolidate DCN vblank/flip handling onto vupdate_no_lock +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 191 ++++++++++++++---- + drivers/gpu/drm/amd/display/dc/core/dc.c | 45 +++++ + drivers/gpu/drm/amd/display/dc/dc.h | 1 + + 3 files changed, 197 insertions(+), 40 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index 93cd70183c188..11affddbb2650 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -677,13 +677,30 @@ static void dm_crtc_high_irq_handler(struct amdgpu_device *adev, + * Deliver pageflip completion events (DCN only). + * + * Since GRPH_PFLIP is not used, VUPDATE_NO_LOCK is the flip latch +- * point. Deliver any pending pageflip completion event from here. ++ * point. Deliver any pending pageflip completion event from here, ++ * once HW has consumed the new address (the OTG no longer reports a ++ * pending flip). + * +- * NOTE: This can deliver an event for a flip that was armed but not yet +- * programmed into HW; that race is closed in a follow-up change by +- * checking the programmed flip status. ++ * Also handle the case here where there aren't any active planes and ++ * DCN HUBP may be clock-gated, so the flip-pending status may be ++ * undefined. + */ +- if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED) { ++ if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ acrtc->event) { ++ ++ if (!dc_get_flip_pending_on_otg(adev->dm.dc, acrtc->otg_inst)) { ++ drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); ++ acrtc->event = NULL; ++ drm_crtc_vblank_put(&acrtc->base); ++ acrtc->pflip_status = AMDGPU_FLIP_NONE; ++ } ++ /* ++ * If the flip is still pending, leave it armed and ++ * retry on the next vupdate. ++ */ ++ } else if (is_dcn && acrtc->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ acrtc->dm_irq_params.active_planes == 0) { ++ + if (acrtc->event) { + drm_crtc_send_vblank_event(&acrtc->base, acrtc->event); + acrtc->event = NULL; +@@ -9995,6 +10012,28 @@ static void amdgpu_dm_enable_self_refresh(struct amdgpu_crtc *acrtc_attach, + } + } + ++static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, ++ struct dm_crtc_state *acrtc_state, ++ bool pflip_update, ++ bool cursor_update) ++{ ++ assert_spin_locked(&acrtc->base.dev->event_lock); ++ ++ if (!acrtc->base.state->event || acrtc_state->active_planes == 0) ++ return; ++ ++ if (pflip_update) { ++ drm_crtc_vblank_get(&acrtc->base); ++ WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); ++ /* Arm flip completion handling and event delivery after programming. */ ++ prepare_flip_isr(acrtc); ++ } else if (cursor_update) { ++ drm_crtc_vblank_get(&acrtc->base); ++ acrtc->event = acrtc->base.state->event; ++ acrtc->base.state->event = NULL; ++ } ++} ++ + static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + struct drm_device *dev, + struct amdgpu_display_manager *dm, +@@ -10018,6 +10057,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + bool cursor_update = false; + bool pflip_present = false; + bool immediate_flip = false; ++ bool flip_latched_during_prog = false; + bool dirty_rects_changed = false; + bool updated_planes_and_streams = false; + struct { +@@ -10254,39 +10294,24 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + usleep_range(1000, 1100); + } + +- /** +- * Prepare the flip event for the pageflip interrupt to handle. +- * +- * This only works in the case where we've already turned on the +- * appropriate hardware blocks (eg. HUBP) so in the transition case +- * from 0 -> n planes we have to skip a hardware generated event +- * and rely on sending it from software. +- */ +- if (acrtc_attach->base.state->event && +- acrtc_state->active_planes > 0) { +- drm_crtc_vblank_get(pcrtc); +- +- spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- +- WARN_ON(acrtc_attach->pflip_status != AMDGPU_FLIP_NONE); +- prepare_flip_isr(acrtc_attach); +- +- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); +- } +- + if (acrtc_state->stream) { + if (acrtc_state->freesync_vrr_info_changed) + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; + } +- } else if (cursor_update && acrtc_state->active_planes > 0) { +- spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- if (acrtc_attach->base.state->event) { +- drm_crtc_vblank_get(pcrtc); +- acrtc_attach->event = acrtc_attach->base.state->event; +- acrtc_attach->base.state->event = NULL; ++ } ++ ++ /* ++ * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for ++ * event delivery. Only GRPH_FLIP handler can send pflip events, and it ++ * only fires if HW latched to the flip. Maintain legacy behavior by ++ * arming event before programming. ++ */ ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { ++ scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ dm_arm_vblank_event(acrtc_attach, acrtc_state, ++ pflip_present, cursor_update); + } +- spin_unlock_irqrestore(&pcrtc->dev->event_lock, flags); + } + + /* Update the planes if changed or disable if we don't have any. */ +@@ -10388,17 +10413,103 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + amdgpu_dm_commit_cursors(state); + + /* +- * On DCN, flip completion is normally delivered from VUPDATE_NO_LOCK. +- * However, an immediate (tearing / async) flip is latched by HW right +- * away and does not wait for the next vupdate, so deliver its +- * completion event here after programming. ++ * DCN specific vblank handling ++ * ============================ ++ * ++ * With the event_lock held, arm the vblank event, and determine whether ++ * deliver it immediately, or in VUPDATE_NO_LOCK IRQ (i.e. HW latch ++ * point) handler. Do this *after* programming so that the IRQ handler ++ * will not deliver the event before HW laches onto the programmed ++ * values: ++ * ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * arm_vblank_event() ++ * vupdate() ++ * vupdate_handler() ++ * cook_timestamp() ++ * # prev flip already latched, ++ * # so flip_latched == true. ++ * if event_armed && flip_latched: ++ * send_vblank_event() ++ * # sent before latch, **BAD!** ++ * hw_program() ++ * vupdate() ++ * **latch** ++ * ++ * There's a consequence of arming after: it's possible for HW to latch ++ * between start of HW programming and acrtc->event/pflip_status arming. ++ * When this happens, the IRQ handler will send the event on the next ++ * immediate latch point, even though HW has already latched. This is ++ * handled by optimistically checking for HW latch after programming, ++ * and if latched, send the event immediately: + * +- * On DCE, GRPH_PFLIP already fires immediately for immediate flips, so +- * this is DCN-only. ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * hw_program() ++ * vupdate() ++ * **latch** ++ * vupdate_handler() ++ * cook_timestamp() ++ * # event_armed == false ++ * # **no event sent!** ++ * arm_vblank_event() ++ * if flip_latched: ++ * **send_vblank_event()** ++ * disarm_vblank_event() ++ * ++ * The IRQ handler is expected to cook the timestamp, but we need to ++ * cook the timestamp before optimistic sending as well. That's because ++ * the following sequence is possible: ++ * ++ * Commit thread IRQ handler HW ++ * ----------------------------------------------------------------- ++ * hw_program() ++ * arm_vblank_event() ++ * vupdate() ++ * **latch** ++ * if flip_latched: ++ * # Need cook before send! ++ * **cook_timestamp()** ++ * send_vblank_event() ++ * disarm_vblank_event() ++ * vupdate_handler() ++ * cook_timestamp() ++ * # event_armed == false ++ * # no event sent! ++ * ++ * Cooking twice is OK, since DRM scanout accurate timestamps report A) ++ * the previous vactive start if currently in vactive, or B) the next ++ * vactive start if currently in vblank (see &get_vblank_counter). 'A)' ++ * is what we want for the optimistic send, and for 'B)', we'll cook a ++ * timestamp no later than the next IRQ handler run. ++ * ++ * The more correct fix is to wrap programming and arming with the ++ * event_lock and thus serializing it with the IRQ handler. However, ++ * there are various sleep-waits within ++ * update_planes_and_stream_adapter() that makes spin locking illegal. ++ * And on full updates, it can take 1-2 frame-times to return (see ++ * commit_planes_for_stream). ++ * ++ * On DCE, GRPH_PFLIP IRQ is used and takes care of this. + */ +- if (immediate_flip && amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) != 0) { + spin_lock_irqsave(&pcrtc->dev->event_lock, flags); +- if (acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && ++ ++ if (updated_planes_and_streams) { ++ flip_latched_during_prog = ++ !dc_get_flip_pending_on_otg(dm->dc, acrtc_attach->otg_inst); ++ } ++ ++ dm_arm_vblank_event(acrtc_attach, acrtc_state, ++ pflip_present, cursor_update); ++ ++ /* ++ * Deliver the event immediately on immediate flip, or on a ++ * update that has already latched. ++ */ ++ if ((immediate_flip || flip_latched_during_prog) && ++ acrtc_attach->pflip_status == AMDGPU_FLIP_SUBMITTED && + acrtc_attach->event) { + drm_crtc_accurate_vblank_count(&acrtc_attach->base); + drm_crtc_send_vblank_event(&acrtc_attach->base, +diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c +index ec194dd3b7985..b58ea80a97659 100644 +--- a/drivers/gpu/drm/amd/display/dc/core/dc.c ++++ b/drivers/gpu/drm/amd/display/dc/core/dc.c +@@ -5628,6 +5628,51 @@ void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src) + dal_irq_service_ack(dc->res_pool->irqs, src); + } + ++/* ++ * dc_get_flip_pending_on_otg() - Check if a GRPH_FLIP is still pending on OTG ++ * ++ * @dc: display core context @otg_inst: OTG instance to query ++ * ++ * Reads the HUBP flip-pending status for the pipe(s) bound to @otg_inst, ++ * returning true if any of them has not yet latched its programmed surface ++ * address. ++ * ++ * Unlike dc_plane_get_status(), this does not take or mutate a dc_plane_state, ++ * so it is safe to call from interrupt context without racing a concurrent ++ * commit that may be updating plane state. ++ * ++ * Return: true if a flip is still pending on the OTG, false otherwise. ++ */ ++bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst) ++{ ++ bool flip_pending = false; ++ int i; ++ ++ if (!dc || !dc->current_state) ++ return false; ++ ++ dc_exit_ips_for_hw_access(dc); ++ ++ for (i = 0; i < dc->res_pool->pipe_count; i++) { ++ struct pipe_ctx *pipe_ctx = &dc->current_state->res_ctx.pipe_ctx[i]; ++ struct hubp *hubp = pipe_ctx->plane_res.hubp; ++ ++ if (!pipe_ctx->plane_state || !pipe_ctx->stream_res.tg) ++ continue; ++ ++ if (pipe_ctx->stream_res.tg->inst != otg_inst) ++ continue; ++ ++ if (hubp && hubp->funcs->hubp_is_flip_pending && ++ hubp->funcs->hubp_is_flip_pending(hubp)) { ++ flip_pending = true; ++ break; ++ } ++ } ++ ++ return flip_pending; ++} ++ + void dc_power_down_on_boot(struct dc *dc) + { + if (dc->ctx->dce_environment != DCE_ENV_VIRTUAL_HW && +diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h +index 61a28e287c1b9..65388a6926b63 100644 +--- a/drivers/gpu/drm/amd/display/dc/dc.h ++++ b/drivers/gpu/drm/amd/display/dc/dc.h +@@ -2738,6 +2738,7 @@ enum dc_irq_source dc_interrupt_to_irq_source( + uint32_t ext_id); + bool dc_interrupt_set(struct dc *dc, enum dc_irq_source src, bool enable); + void dc_interrupt_ack(struct dc *dc, enum dc_irq_source src); ++bool dc_get_flip_pending_on_otg(struct dc *dc, int otg_inst); + enum dc_irq_source dc_get_hpd_irq_source_at_index( + struct dc *dc, uint32_t link_index); + +-- +2.53.0 + diff --git a/queue-7.1/drm-amd-display-exit-idle-optimizations-before-progr.patch b/queue-7.1/drm-amd-display-exit-idle-optimizations-before-progr.patch new file mode 100644 index 0000000000..2e9f8e0883 --- /dev/null +++ b/queue-7.1/drm-amd-display-exit-idle-optimizations-before-progr.patch @@ -0,0 +1,167 @@ +From 5ea1eebeab0239eeec86e3f0456001f7e5eca9a2 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Wed, 5 Aug 2026 12:33:14 -0400 +Subject: drm/amd/display: Exit idle optimizations before programming + +From: Leo Li + +[ Upstream commit 8419331e64d92a8de5fc4feef0e305f201fb8b33 ] + +[Why] + +We need to exit PSR/IPS before programming. Before calling DC for +programming in amdgpu_dm_commit_planes(), there's a +vblank_control_workqueue flush. This waits for IPS and PSR exit. (See +drm_vblank_on/off() > amdgpu_dm_crtc_set_vblank() --queue_work()-> +amdgpu_dm_crtc_vblank_control_worker()) + +Prior to the tagged "Fixes:" change, drm_vblank_get() was called before +the workqueue flush. This ordering ensures that PSR exit occurred before +programming. After the "Fixes:" change, drm_vblank_get() is called after +the workqueue flush, leading to programming while idle optimizations are +still active. This can lead to incorrect flip_pending detection used by +vblank event delivery. + +[How] + +Split the vblank_get() component of `dm_arm_vblank_event()` into +`dm_arm_vblank_event_pre_programming()`, which is called before +programming. Call it before the vblank_control_workqueue flush. + +Includes a drive-by cleanup of prepare_flip_isr(): the only caller is +dm_arm_vblank_event() and it's simple enough to roll-in. + +v2: Fix checkpatch formatting warning on + drm_arm_vblank_event_pre_programming() arg alignment. + +Fixes: 48ab86360af1 ("drm/amd/display: check GRPH_FLIP status before sending event") +Cc: stable@vger.kernel.org +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/4141#note_3583205 +Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/5527 +Assisted-by: Codex:gpt-5.6-sol +Assisted-by: Claude:opus-5 +Suggested-by: David Weber +Signed-off-by: Leo Li +Reviewed-by: Mario Limonciello (AMD) +Signed-off-by: Alex Deucher +(cherry picked from commit 05984e29520a28c27f5a2388742c957a6a87ee7a) +(cherry picked from commit 8419331e64d92a8de5fc4feef0e305f201fb8b33) +Signed-off-by: Sasha Levin +--- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 78 +++++++++++-------- + 1 file changed, 46 insertions(+), 32 deletions(-) + +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index 11affddbb2650..40d82a3eeec05 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -9656,25 +9656,6 @@ static void remove_stream(struct amdgpu_device *adev, + acrtc->enabled = false; + } + +-static void prepare_flip_isr(struct amdgpu_crtc *acrtc) +-{ +- +- assert_spin_locked(&acrtc->base.dev->event_lock); +- WARN_ON(acrtc->event); +- +- acrtc->event = acrtc->base.state->event; +- +- /* Set the flip status */ +- acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; +- +- /* Mark this event as consumed */ +- acrtc->base.state->event = NULL; +- +- drm_dbg_state(acrtc->base.dev, +- "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", +- acrtc->crtc_id); +-} +- + static void update_freesync_state_on_stream( + struct amdgpu_display_manager *dm, + struct dm_crtc_state *new_crtc_state, +@@ -10023,17 +10004,47 @@ static void dm_arm_vblank_event(struct amdgpu_crtc *acrtc, + return; + + if (pflip_update) { +- drm_crtc_vblank_get(&acrtc->base); + WARN_ON(acrtc->pflip_status != AMDGPU_FLIP_NONE); +- /* Arm flip completion handling and event delivery after programming. */ +- prepare_flip_isr(acrtc); ++ WARN_ON(acrtc->event); ++ ++ acrtc->pflip_status = AMDGPU_FLIP_SUBMITTED; ++ acrtc->event = acrtc->base.state->event; ++ acrtc->base.state->event = NULL; ++ ++ drm_dbg_state(acrtc->base.dev, ++ "crtc:%d, pflip_stat:AMDGPU_FLIP_SUBMITTED\n", ++ acrtc->crtc_id); + } else if (cursor_update) { +- drm_crtc_vblank_get(&acrtc->base); + acrtc->event = acrtc->base.state->event; + acrtc->base.state->event = NULL; + } + } + ++/** ++ * dm_arm_vblank_event_pre_programming - Prepare for programming ++ * @acrtc: The amdgpu CRTC to prepare ++ * @acrtc_state: The new CRTC state ++ * @pflip_update: Whether a page flip is being programmed ++ * @cursor_update: Whether a cursor update is being programmed ++ * ++ * Grab a reference on the vblank counter if a page flip or cursor update is to ++ * be programmed. Do this before programming so the HW is not in any ++ * idle-optimized state (such as PSR). ++ */ ++static void dm_arm_vblank_event_pre_programming(struct amdgpu_crtc *acrtc, ++ struct dm_crtc_state *acrtc_state, ++ bool pflip_update, ++ bool cursor_update) ++{ ++ assert_spin_locked(&acrtc->base.dev->event_lock); ++ ++ if (!acrtc->base.state->event || acrtc_state->active_planes == 0) ++ return; ++ ++ if (pflip_update || cursor_update) ++ drm_crtc_vblank_get(&acrtc->base); ++} ++ + static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + struct drm_device *dev, + struct amdgpu_display_manager *dm, +@@ -10301,16 +10312,19 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + } + } + +- /* +- * DCE depends on a combination of GRPH_FLIP, VLINE0, and VUPDATE for +- * event delivery. Only GRPH_FLIP handler can send pflip events, and it +- * only fires if HW latched to the flip. Maintain legacy behavior by +- * arming event before programming. +- */ +- if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { +- scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ scoped_guard(spinlock_irqsave, &pcrtc->dev->event_lock) { ++ dm_arm_vblank_event_pre_programming(acrtc_attach, acrtc_state, ++ pflip_present, ++ cursor_update); ++ /* ++ * DCE depends on a combination of GRPH_FLIP, VLINE0, and ++ * VUPDATE for event delivery. Only GRPH_FLIP handler can send ++ * pflip events, and it only fires if HW latched to the flip. ++ * Maintain legacy behavior by arming event before programming. ++ */ ++ if (amdgpu_ip_version(dm->adev, DCE_HWIP, 0) == 0) { + dm_arm_vblank_event(acrtc_attach, acrtc_state, +- pflip_present, cursor_update); ++ pflip_present, cursor_update); + } + } + +-- +2.53.0 + diff --git a/queue-7.1/series b/queue-7.1/series index 52bb83ce0d..ce4692ce8c 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -434,3 +434,5 @@ drm-xe-set-ttm-device-beneficial_order-to-9-2m.patch drm-exec-remove-the-index-parameter-from-drm_exec_for_each_locked_obj.patch drm-xe-wait-on-external-bo-kernel-fences-in-exec-ioctl.patch media-chips-media-wave5-support-cbp-profile.patch +drm-amd-display-check-grph_flip-status-before-sendin.patch +drm-amd-display-exit-idle-optimizations-before-progr.patch