[RFC PATCH 5/5] drm/amd/display: add PSR recovery fault injection
David Weber <[email protected]> Wed, 5 Aug 2026 13:52:48 +0200
| Newsgroups | org.freedesktop.lists.amd-gfx |
|---|---|
| Message-ID | <f1da6bf58b5464f1b6e5b550f7e4627aed39ae64.1785929873.git.weber.aulendorf@gmail.com> |
Introduce two write-only debugfs controls for testing PSR recovery.
amdgpu_dm_trigger_psr_recovery directly queues the PSR recovery worker
without first creating a display or firmware failure. Trigger it with:
echo 1 > /sys/kernel/debug/dri/0/amdgpu_dm_trigger_psr_recovery
amdgpu_dm_halt_dmub_for_psr_recovery selects an active PSR-enabled eDP
stream, enters PSR, halts DMUB firmware, and requests a synchronous PSR
exit. The failed exit should schedule the normal PSR recovery path.
Trigger it with:
echo 0xDEADDEAD > \
/sys/kernel/debug/dri/0/amdgpu_dm_halt_dmub_for_psr_recovery
Halting DMUB is destructive until reset. The second control therefore
requires the existing STOP_FW response value 0xDEADDEAD as a safety
cookie. Both controls refuse to run when GPU recovery is disabled, no
reset domain is available, teardown has started, or another PSR recovery
is pending.
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: David Weber <[email protected]>
---
.../amd/display/amdgpu_dm/amdgpu_dm_debugfs.c | 177 ++++++++++++++++++
1 file changed, 177 insertions(+)
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
index 7db38ad3f848..a80e13b98ad7 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_debugfs.c
@@ -29,6 +29,7 @@
#include <media/cec-notifier.h>
#include "dc.h"
+#include "core_types.h"
#include "amdgpu.h"
#include "amdgpu_dm.h"
#include "amdgpu_dm_debugfs.h"
@@ -4483,6 +4484,176 @@ DEFINE_DEBUGFS_ATTRIBUTE(skip_detection_link_training_fops,
skip_detection_link_training_get,
skip_detection_link_training_set, "%llu\n");
+static int psr_recovery_test_available(struct amdgpu_device *adev)
+{
+ unsigned long flags;
+ bool pending;
+ bool stopping;
+
+ if (!amdgpu_device_should_recover_gpu(adev) || !adev->reset_domain)
+ return -EOPNOTSUPP;
+
+ spin_lock_irqsave(&adev->dm.psr_recovery_lock, flags);
+ pending = adev->dm.psr_recovery_pending;
+ stopping = adev->dm.psr_recovery_stopping;
+ spin_unlock_irqrestore(&adev->dm.psr_recovery_lock, flags);
+
+ if (stopping)
+ return -ESHUTDOWN;
+
+ return pending ? -EBUSY : 0;
+}
+
+static int trigger_psr_recovery_set(void *data, u64 val)
+{
+ struct amdgpu_device *adev = data;
+ int ret;
+
+ if (val != 1)
+ return -EINVAL;
+ if (!adev->dm.dc || !adev->dm.dc->ctx)
+ return -ENODEV;
+ ret = psr_recovery_test_available(adev);
+ if (ret)
+ return ret;
+
+ drm_warn(adev_to_drm(adev),
+ "injecting a fatal PSR failure to test display recovery\n");
+ if (!amdgpu_dm_schedule_psr_recovery(&adev->dm, NULL, NULL))
+ return -EIO;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(trigger_psr_recovery_fops, NULL,
+ trigger_psr_recovery_set, "%llu\n");
+
+static int halt_dmub_for_psr_recovery_set(void *data, u64 val)
+{
+ struct amdgpu_device *adev = data;
+ struct dc *dc = adev->dm.dc;
+ struct dc_dmub_srv *dc_dmub_srv;
+ struct dmub_srv *dmub;
+ struct dc_link *link = NULL;
+ enum dmub_status status;
+ u32 response = 0;
+ bool allow_active;
+ bool pending;
+ bool programmed;
+ unsigned long flags;
+ int ret = 0;
+ int i;
+
+ /*
+ * Halting DMUB is destructive until reset. Require the STOP_FW ABI
+ * response cookie to make an accidental write less likely.
+ */
+ if (val != DMUB_GPINT__STOP_FW_RESPONSE)
+ return -EINVAL;
+ ret = psr_recovery_test_available(adev);
+ if (ret)
+ return ret;
+ if (!dc || !dc->ctx || !dc->ctx->dmub_srv ||
+ !dc->res_pool || !dc->res_pool->psr)
+ return -ENODEV;
+
+ dc_dmub_srv = dc->ctx->dmub_srv;
+ dmub = dc_dmub_srv->dmub;
+ if (!dmub)
+ return -ENODEV;
+
+ mutex_lock(&adev->dm.dc_lock);
+ if (dc->current_state) {
+ for (i = 0; i < dc->current_state->stream_count; i++) {
+ struct dc_stream_state *stream =
+ dc->current_state->streams[i];
+
+ if (stream && stream->link &&
+ stream->link->connector_signal == SIGNAL_TYPE_EDP &&
+ stream->link->psr_settings.psr_feature_enabled) {
+ link = stream->link;
+ break;
+ }
+ }
+ }
+
+ if (!link) {
+ mutex_unlock(&adev->dm.dc_lock);
+ return -ENODEV;
+ }
+ ret = psr_recovery_test_available(adev);
+ if (ret)
+ goto unlock;
+
+ if (dc_dmub_srv->idle_allowed)
+ dc_dmub_srv_apply_idle_power_optimizations(dc, false);
+
+ /* Enter PSR before halting firmware so the test exercises failed exit. */
+ link->psr_settings.psr_allow_active_valid = false;
+ allow_active = true;
+ programmed = dc_link_set_psr_allow_active(link, &allow_active, true,
+ false, NULL);
+ if (!programmed) {
+ drm_err(adev_to_drm(adev),
+ "failed to enter PSR before halting DMUB\n");
+ ret = -EIO;
+ goto unlock;
+ }
+
+ drm_warn(adev_to_drm(adev),
+ "halting DMUB firmware to test PSR exit recovery\n");
+ status = dmub_srv_send_gpint_command(dmub, DMUB_GPINT__STOP_FW, 0,
+ 100000);
+ if (status != DMUB_STATUS_OK) {
+ drm_err(adev_to_drm(adev),
+ "failed to halt DMUB firmware: %d; recovering\n", status);
+ if (!amdgpu_dm_schedule_psr_recovery(&adev->dm, NULL, NULL))
+ drm_err(adev_to_drm(adev),
+ "failed to schedule recovery after DMUB halt error\n");
+ ret = -EIO;
+ goto unlock;
+ }
+
+ for (i = 0; i < 100000; i++) {
+ status = dmub_srv_get_gpint_response(dmub, &response);
+ if (status == DMUB_STATUS_OK &&
+ response == DMUB_GPINT__STOP_FW_RESPONSE)
+ break;
+ udelay(1);
+ }
+
+ if (response != DMUB_GPINT__STOP_FW_RESPONSE)
+ drm_warn(adev_to_drm(adev),
+ "DMUB halt response timed out; testing PSR exit anyway\n");
+
+ allow_active = false;
+ programmed = dc_link_set_psr_allow_active(link, &allow_active, true,
+ false, NULL);
+ spin_lock_irqsave(&adev->dm.psr_recovery_lock, flags);
+ pending = adev->dm.psr_recovery_pending;
+ spin_unlock_irqrestore(&adev->dm.psr_recovery_lock, flags);
+ if (programmed) {
+ drm_err(adev_to_drm(adev),
+ "PSR exit unexpectedly succeeded after DMUB halt\n");
+ ret = -EIO;
+ } else if (!pending) {
+ drm_err(adev_to_drm(adev),
+ "PSR exit timeout did not schedule recovery\n");
+ ret = -EIO;
+ } else {
+ drm_info(adev_to_drm(adev),
+ "PSR exit timeout scheduled recovery\n");
+ }
+
+unlock:
+ mutex_unlock(&adev->dm.dc_lock);
+
+ return ret;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(halt_dmub_for_psr_recovery_fops, NULL,
+ halt_dmub_for_psr_recovery_set, "%llu\n");
+
/*
* Dumps the DCC_EN bit for each pipe.
* Example usage: cat /sys/kernel/debug/dri/0/amdgpu_dm_dcc_en
@@ -4579,6 +4750,12 @@ void dtn_debugfs_init(struct amdgpu_device *adev)
debugfs_create_file_unsafe("amdgpu_dm_skip_detection_link_training", 0644, root, adev,
&skip_detection_link_training_fops);
+ debugfs_create_file_unsafe("amdgpu_dm_trigger_psr_recovery", 0200,
+ root, adev, &trigger_psr_recovery_fops);
+ debugfs_create_file_unsafe("amdgpu_dm_halt_dmub_for_psr_recovery", 0200,
+ root, adev,
+ &halt_dmub_for_psr_recovery_fops);
+
debugfs_create_file_unsafe("amdgpu_dm_dmub_tracebuffer", 0644, root,
adev, &dmub_tracebuffer_fops);
--
2.54.0