]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
Fixes for all trees
authorSasha Levin <sashal@kernel.org>
Fri, 7 Aug 2026 11:44:41 +0000 (07:44 -0400)
committerSasha Levin <sashal@kernel.org>
Fri, 7 Aug 2026 11:44:41 +0000 (07:44 -0400)
Signed-off-by: Sasha Levin <sashal@kernel.org>
queue-6.1/bluetooth-6lowpan-fix-using-chan-conn-as-indication-.patch [new file with mode: 0644]
queue-6.1/bluetooth-l2cap-fix-uaf-in-channel-timeout-by-holdin.patch [new file with mode: 0644]
queue-6.1/series
queue-6.18/drm-amd-display-check-grph_flip-status-before-sendin.patch [new file with mode: 0644]
queue-6.18/drm-amd-display-exit-idle-optimizations-before-progr.patch [new file with mode: 0644]
queue-6.18/series
queue-7.1/drm-amd-display-check-grph_flip-status-before-sendin.patch [new file with mode: 0644]
queue-7.1/drm-amd-display-exit-idle-optimizations-before-progr.patch [new file with mode: 0644]
queue-7.1/series

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 (file)
index 0000000..64a33b9
--- /dev/null
@@ -0,0 +1,68 @@
+From 4f13e01c982b697fff9f8170d4b12aed23a12b94 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+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 <luiz.von.dentz@intel.com>
+
+[ 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 <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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 (file)
index 0000000..3c2c18f
--- /dev/null
@@ -0,0 +1,252 @@
+From 7b33f1a57bd459f54a4488183a2eeea2fa39ed40 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 6 Aug 2026 00:52:14 +0000
+Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref
+
+From: Marco Elver <elver@google.com>
+
+[ 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:
+|  <TASK>
+|  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
+|  </TASK>
+|
+| 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: <stable@vger.kernel.org>
+Cc: Siwei Zhang <oss@fourdim.xyz>
+Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+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 <elver@google.com>
+Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Todd Kjos <tkjos@google.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ 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
+
index f881727b933929683c801579ed139e64df982001..b8bd8cbfe55c1a65f1e162d68bb0e78718a39c07 100644 (file)
@@ -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 (file)
index 0000000..3d26a0a
--- /dev/null
@@ -0,0 +1,397 @@
+From 5495b06776985b09d94ff2a2a0992ed1c15c527f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 13:57:48 -0400
+Subject: drm/amd/display: check GRPH_FLIP status before sending event
+
+From: Leo Li <sunpeng.li@amd.com>
+
+[ 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) <superm1@kernel.org>
+Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Signed-off-by: Leo Li <sunpeng.li@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(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 <sashal@kernel.org>
+---
+ .../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 (file)
index 0000000..18ac5b9
--- /dev/null
@@ -0,0 +1,167 @@
+From c257a7c06ea915c5f661704b6a4777fdb7886b99 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 5 Aug 2026 12:33:14 -0400
+Subject: drm/amd/display: Exit idle optimizations before programming
+
+From: Leo Li <sunpeng.li@amd.com>
+
+[ 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 <weber.aulendorf@gmail.com>
+Signed-off-by: Leo Li <sunpeng.li@amd.com>
+Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 05984e29520a28c27f5a2388742c957a6a87ee7a)
+(cherry picked from commit 8419331e64d92a8de5fc4feef0e305f201fb8b33)
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ .../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
+
index 57cb4f230241f7ec6fcb2a73c2c3f54e83eff08d..ba44432ed95df402bc91ad78c7659a0e053008bc 100644 (file)
@@ -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 (file)
index 0000000..c657fbb
--- /dev/null
@@ -0,0 +1,397 @@
+From 03feabd751443ed9e2102ae995226dae041ddb15 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 13:37:43 -0400
+Subject: drm/amd/display: check GRPH_FLIP status before sending event
+
+From: Leo Li <sunpeng.li@amd.com>
+
+[ 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) <superm1@kernel.org>
+Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Signed-off-by: Leo Li <sunpeng.li@amd.com>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(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 <sashal@kernel.org>
+---
+ .../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 (file)
index 0000000..2e9f8e0
--- /dev/null
@@ -0,0 +1,167 @@
+From 5ea1eebeab0239eeec86e3f0456001f7e5eca9a2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 5 Aug 2026 12:33:14 -0400
+Subject: drm/amd/display: Exit idle optimizations before programming
+
+From: Leo Li <sunpeng.li@amd.com>
+
+[ 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 <weber.aulendorf@gmail.com>
+Signed-off-by: Leo Li <sunpeng.li@amd.com>
+Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
+Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
+(cherry picked from commit 05984e29520a28c27f5a2388742c957a6a87ee7a)
+(cherry picked from commit 8419331e64d92a8de5fc4feef0e305f201fb8b33)
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ .../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
+
index 52bb83ce0dc9378f25ac2f3348547eaa51d346a7..ce4692ce8c238d08334914c6b7c4fffad0437d95 100644 (file)
@@ -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