[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