From: Rodrigo Vivi <rodrigo.vivi@intel.com>
To: Karthik Poosa <karthik.poosa@intel.com>
Cc: <igt-dev@lists.freedesktop.org>, <anshuman.gupta@intel.com>,
<badal.nilawar@intel.com>, <raag.jadav@intel.com>,
<riana.tauro@intel.com>, <sk.anirban@intel.com>,
<mallesh.koujalagi@intel.com>, <soham.purkait@intel.com>,
<ravi.kishore.koppuravuri@intel.com>
Subject: Re: [PATCH i-g-t] tests/intel/xe_pm: Validate root-port D3cold entry on system suspend
Date: Wed, 11 Nov 2026 13:45:27 -0500 [thread overview]
Message-ID: <avS3gcuObbGGHPbT@intel.com> (raw)
In-Reply-To: <20261102095802.3419285-2-karthik.poosa@intel.com>
On Mon, Nov 02, 2026 at 03:28:02PM +0530, Karthik Poosa wrote:
> When D3cold is supported and allowed, check the root port's ACPI
> transition log across system suspend and resume for a D0 -> D3cold
> transition. Skip the transition assertion if the root port is already
> in D3cold at suspend entry.
>
> Signed-off-by: Karthik Poosa <karthik.poosa@intel.com>
> Assisted-by: LLM
> ---
> tests/intel/xe_pm.c | 234 +++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 221 insertions(+), 13 deletions(-)
>
> diff --git a/tests/intel/xe_pm.c b/tests/intel/xe_pm.c
> index 543f46f5b..2eb70a383 100644
> --- a/tests/intel/xe_pm.c
> +++ b/tests/intel/xe_pm.c
> @@ -12,8 +12,10 @@
> */
>
> #include <dirent.h>
> +#include <errno.h>
> #include <limits.h>
> #include <fcntl.h>
> +#include <stdio.h>
> #include <string.h>
> #include <sys/ioctl.h>
>
> @@ -63,6 +65,9 @@ typedef struct {
>
> uint64_t orig_threshold;
> int fw_handle = -1;
> +static int fd_kmsg = -1;
> +static char d3cold_kmsg_entry[512];
> +static bool acpi_d3cold_debug_enabled_by_test;
>
> static pthread_mutex_t suspend_lock = PTHREAD_MUTEX_INITIALIZER;
> static pthread_cond_t suspend_cond = PTHREAD_COND_INITIALIZER;
> @@ -192,6 +197,120 @@ static void cleanup_d3(device_t device)
> dpms_on_off(device, DRM_MODE_DPMS_ON);
> }
>
> +static int count_kmsg_entries(int kmsg, const char *entry)
> +{
> + char buf[4096];
> + char *kmsg_entry, *p;
> + const unsigned char *src;
> + size_t entry_len = strlen(entry);
> + int count = 0;
> + ssize_t len;
> +
> + igt_assert(entry_len <= (SIZE_MAX - 1) / 4);
> + kmsg_entry = malloc(4 * entry_len + 1);
> + igt_assert(kmsg_entry);
> +
> + /* Match /dev/kmsg's escaping of backslashes and non-printable bytes. */
> + for (src = (const unsigned char *)entry, p = kmsg_entry; *src; src++) {
> + if (*src < ' ' || *src >= 127 || *src == '\\')
> + p += sprintf(p, "\\x%02x", (unsigned int)*src);
> + else
> + *p++ = *src;
> + }
> + *p = '\0';
> +
> + errno = 0;
> + while ((len = read(kmsg, buf, sizeof(buf) - 1)) > 0) {
> + buf[len] = '\0';
> + if (strstr(buf, kmsg_entry)) {
> + igt_debug("Found dmesg entry: %s\n", buf);
> + count++;
> + }
> + }
> +
> + free(kmsg_entry);
> +
> + if (len == 0)
> + return count;
> +
> + igt_assert_f(len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK),
> + "Unexpected /dev/kmsg read result len=%zd errno=%d\n", len, errno);
> +
> + return count;
> +}
> +
> +static void build_rp_d3cold_kmsg_str(struct pci_device *pci_root, char *buf, size_t len)
> +{
> + char path[PATH_MAX], fw_path[256];
> + ssize_t n;
> + int fd;
> +
> + snprintf(path, sizeof(path),
> + "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/firmware_node/path",
> + pci_root->domain, pci_root->bus, pci_root->dev, pci_root->func);
> +
> + fd = open(path, O_RDONLY);
> + igt_require_fd(fd);
> + n = read(fd, fw_path, sizeof(fw_path) - 1);
> + igt_assert_lte(0, (int)n);
> + fw_path[n] = '\0';
> + if (n > 0 && fw_path[n - 1] == '\n')
> + fw_path[n - 1] = '\0';
> + close(fd);
> +
> + snprintf(buf, len, "%s: PM: Power state change: D0 -> D3cold", fw_path);
I'm a bit confused by this message and the order, but I will trust
more your tests...
> +}
> +
> +#define ACPI_DYNDDBG_CONTROL "/sys/kernel/debug/dynamic_debug/control"
> +#define ACPI_D3COLD_DYNDDBG_QUERY \
> + "file drivers/acpi/device_pm.c format \"Power state change: %s -> %s\""
> +
> +static bool acpi_d3cold_debug_is_enabled(void)
> +{
> + char line[1024];
> + FILE *fp;
> + bool enabled = false;
> +
> + fp = fopen(ACPI_DYNDDBG_CONTROL, "r");
> + if (!fp)
> + return false;
> +
> + while (fgets(line, sizeof(line), fp)) {
> + char *message = strstr(line, "Power state change: %s -> %s");
> + char *flags = strchr(line, '=');
> +
> + if (message && flags && flags < message && memchr(flags, 'p', message - flags)) {
> + enabled = true;
> + break;
> + }
> + }
> +
> + fclose(fp);
> + return enabled;
> +}
> +
> +static bool set_acpi_d3cold_debug(bool enable)
> +{
> + FILE *fp;
> + int ret;
> +
> + fp = fopen(ACPI_DYNDDBG_CONTROL, "w");
> + if (!fp)
> + return false;
> +
> + ret = fprintf(fp, "%s %sp\n", ACPI_D3COLD_DYNDDBG_QUERY, enable ? "+" : "-");
> + if (fclose(fp))
> + return false;
> +
> + return ret > 0;
> +}
> +
> +static void restore_acpi_d3cold_debug(int sig)
> +{
> + if (acpi_d3cold_debug_enabled_by_test)
> + set_acpi_d3cold_debug(false);
> +}
> +
> static bool in_d3(device_t device, enum igt_acpi_d_state state)
> {
> uint16_t val;
> @@ -228,6 +347,85 @@ static void close_fw_handle(int sig)
> close(fw_handle);
> }
>
> +static void close_d3cold_kmsg_handle(int sig)
> +{
> + if (fd_kmsg < 0)
> + return;
> +
> + close(fd_kmsg);
> + fd_kmsg = -1;
> +}
> +
> +static void start_d3cold_kmsg_logging(void)
> +{
> + igt_require_fd(fd_kmsg);
> + igt_assert_neq(lseek(fd_kmsg, 0, SEEK_END), -1);
> +}
> +
> +static void check_d3cold_kmsg_count(void)
> +{
> + int count;
> +
> + igt_require_fd(fd_kmsg);
> + count = count_kmsg_entries(fd_kmsg, d3cold_kmsg_entry);
> + igt_info("Root port D3cold entry count=%d\n", count);
> + igt_assert_f(count > 0,
> + "No root port D3cold entry detected during system suspend\n");
> +}
> +
> +static bool d3cold_validation_enabled(device_t device)
> +{
> + uint32_t d3cold_allowed;
> +
> + if (!igt_pm_acpi_d3cold_supported(device.pci_root))
> + return false;
> +
> + igt_pm_get_d3cold_allowed(device.pci_slot_name, &d3cold_allowed);
> +
> + return d3cold_allowed;
> +}
> +
> +static void prepare_d3cold_kmsg(device_t device)
> +{
> + igt_require_f(d3cold_validation_enabled(device),
> + "D3Cold validation requires a D3Cold-capable root port and d3cold_allowed=1\n");
> +
> + if (fd_kmsg < 0) {
> + build_rp_d3cold_kmsg_str(device.pci_root, d3cold_kmsg_entry,
> + sizeof(d3cold_kmsg_entry));
> + fd_kmsg = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
> + igt_require_fd(fd_kmsg);
> + }
> +
> + if (!acpi_d3cold_debug_is_enabled()) {
> + igt_require_f(set_acpi_d3cold_debug(true),
> + "ACPI D3Cold dynamic debug is unavailable\n");
> + acpi_d3cold_debug_enabled_by_test = true;
> + igt_require_f(acpi_d3cold_debug_is_enabled(),
> + "Failed to enable ACPI D3Cold dynamic debug\n");
> + }
> +}
> +
> +static void suspend_check_d3cold(device_t device, enum igt_suspend_state s_state,
> + enum igt_suspend_test test)
> +{
> + igt_require_f(d3cold_validation_enabled(device),
> + "D3Cold validation requires a D3Cold-capable root port and d3cold_allowed=1\n");
This makes all the generic suspend subtests skip on machines without d3cold.
Please ensure this is only called or checked in d3cold related tests
> +
> + if (igt_pm_get_acpi_real_d_state(device.pci_root) == IGT_ACPI_D3Cold) {
> + igt_info("Root port already in D3cold at suspend entry; skip transition check\n");
> + igt_system_suspend_autoresume(s_state, test);
> + return;
> + }
> +
> + prepare_d3cold_kmsg(device);
> +
> + /* Seek to end before suspend so only new messages are scanned */
> + start_d3cold_kmsg_logging();
> + igt_system_suspend_autoresume(s_state, test);
> + check_d3cold_kmsg_count();
> +}
> +
> #define MAX_VMAS 2
>
> static void*
> @@ -578,7 +776,7 @@ child_exec(void *arguments)
> static void
> test_exec(device_t device, int n_exec_queues, int n_execs,
> enum igt_suspend_state s_state, enum igt_acpi_d_state d_state,
> - unsigned int flags)
> + unsigned int flags, bool validate_d3cold)
> {
> enum igt_suspend_test test = s_state == SUSPEND_STATE_DISK ?
> SUSPEND_TEST_DEVICES : SUSPEND_TEST_NONE;
> @@ -587,6 +785,10 @@ test_exec(device_t device, int n_exec_queues, int n_execs,
> pthread_t threads[65]; /* MAX_ENGINES + 1 */
> child_exec_args args;
>
> + if (validate_d3cold && n_execs > 1 && s_state != NO_SUSPEND) {
> + prepare_d3cold_kmsg(device);
> + }
> +
> xe_for_each_engine(device.fd_xe, eci) {
> args.device = device;
> args.eci = eci;
> @@ -607,7 +809,10 @@ test_exec(device_t device, int n_exec_queues, int n_execs,
> }
>
> if (n_execs > 1 && s_state != NO_SUSPEND) {
> - igt_system_suspend_autoresume(s_state, test);
> + if (validate_d3cold)
> + suspend_check_d3cold(device, s_state, test);
> + else
> + igt_system_suspend_autoresume(s_state, test);
>
> pthread_mutex_lock(&suspend_lock);
> pthread_cond_broadcast(&suspend_cond);
> @@ -845,7 +1050,7 @@ static void test_mocs_suspend_resume(device_t device, enum igt_suspend_state s_s
> enum igt_suspend_test test = s_state == SUSPEND_STATE_DISK ?
> SUSPEND_TEST_DEVICES : SUSPEND_TEST_NONE;
>
> - igt_system_suspend_autoresume(s_state, test);
> + suspend_check_d3cold(device, s_state, test);
> }
> }
> igt_assert(igt_debugfs_exists(device.fd_xe, path, O_RDONLY));
> @@ -981,7 +1186,7 @@ int igt_main()
> igt_device_get_pci_slot_name(device.fd_xe, device.pci_slot_name);
>
> /* Always perform initial once-basic exec checking for health */
> - test_exec(device, 1, 1, NO_SUSPEND, NO_RPM, 0);
> + test_exec(device, 1, 1, NO_SUSPEND, NO_RPM, 0, false);
>
> igt_pm_get_d3cold_allowed(device.pci_slot_name, &d3cold_allowed);
> has_runtime_pm = igt_setup_runtime_pm(device.fd_xe);
> @@ -989,6 +1194,8 @@ int igt_main()
> device.res = drmModeGetResources(device.fd_xe);
>
> igt_install_exit_handler(igt_drm_debug_mask_reset_exit_handler);
> + igt_install_exit_handler(close_d3cold_kmsg_handle);
> + igt_install_exit_handler(restore_acpi_d3cold_debug);
> update_debug_mask_if_ci(DRM_UT_KMS);
> errno = 0;
> }
> @@ -997,28 +1204,28 @@ int igt_main()
> igt_subtest_f("%s-basic", s->name) {
> enum igt_suspend_test test = s->state == SUSPEND_STATE_DISK ?
> SUSPEND_TEST_DEVICES : SUSPEND_TEST_NONE;
> - igt_system_suspend_autoresume(s->state, test);
> + suspend_check_d3cold(device, s->state, test);
s4 shouldn't check for d3cold... I believe not even s3... so, let's limit
the scope of this test
> }
>
> igt_subtest_f("%s-basic-exec", s->name) {
> - test_exec(device, 1, 2, s->state, NO_RPM, 0);
> + test_exec(device, 1, 2, s->state, NO_RPM, 0, true);
> }
>
> igt_subtest_f("%s-exec-after", s->name) {
> enum igt_suspend_test test = s->state == SUSPEND_STATE_DISK ?
> SUSPEND_TEST_DEVICES : SUSPEND_TEST_NONE;
>
> - igt_system_suspend_autoresume(s->state, test);
> - test_exec(device, 1, 2, NO_SUSPEND, NO_RPM, 0);
> + suspend_check_d3cold(device, s->state, test);
> + test_exec(device, 1, 2, NO_SUSPEND, NO_RPM, 0, false);
> }
>
> igt_subtest_f("%s-multiple-execs", s->name) {
> - test_exec(device, 16, 32, s->state, NO_RPM, 0);
> + test_exec(device, 16, 32, s->state, NO_RPM, 0, true);
> }
>
> for (const struct vm_op *op = vm_op; op->name; op++) {
> igt_subtest_f("%s-vm-bind-%s", s->name, op->name) {
> - test_exec(device, 16, 32, s->state, NO_RPM, op->flags);
> + test_exec(device, 16, 32, s->state, NO_RPM, op->flags, true);
> }
> }
>
> @@ -1026,7 +1233,8 @@ int igt_main()
> igt_subtest_f("%s-%s-basic-exec", s->name, d->name) {
> igt_require_f(has_runtime_pm, "Runtime PM not available\n");
> igt_assert(setup_d3(device, d->state));
> - test_exec(device, 1, 2, s->state, NO_RPM, 0);
> + test_exec(device, 1, 2, s->state, NO_RPM, 0,
> + d->state == IGT_ACPI_D3Cold);
> cleanup_d3(device);
> }
> }
> @@ -1050,7 +1258,7 @@ int igt_main()
> igt_subtest_f("%s-basic-exec", d->name) {
> igt_require_f(has_runtime_pm, "Runtime PM not available\n");
> igt_assert(setup_d3(device, d->state));
> - test_exec(device, 1, 1, NO_SUSPEND, d->state, 0);
> + test_exec(device, 1, 1, NO_SUSPEND, d->state, 0, false);
> cleanup_d3(device);
> }
>
> @@ -1065,7 +1273,7 @@ int igt_main()
> igt_subtest_f("%s-multiple-execs", d->name) {
> igt_require_f(has_runtime_pm, "Runtime PM not available\n");
> igt_assert(setup_d3(device, d->state));
> - test_exec(device, 16, 32, NO_SUSPEND, d->state, 0);
> + test_exec(device, 16, 32, NO_SUSPEND, d->state, 0, false);
> cleanup_d3(device);
> }
>
> --
> 2.25.1
>
next prev parent reply other threads:[~2026-11-11 18:48 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 9:58 [PATCH i-g-t 0/1] tests/intel/xe_pm: Check root-port D3cold entry Karthik Poosa
2026-11-02 9:58 ` [PATCH i-g-t] tests/intel/xe_pm: Validate root-port D3cold entry on system suspend Karthik Poosa
2026-11-11 18:45 ` Rodrigo Vivi [this message]
2026-11-02 19:45 ` ✓ Xe.CI.BAT: success for " Patchwork
2026-11-02 19:46 ` ✓ i915.CI.BAT: " Patchwork
2026-11-02 20:35 ` ✓ Xe.CI.FULL: " Patchwork
2026-11-02 21:31 ` ✓ i915.CI.Full: " Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=avS3gcuObbGGHPbT@intel.com \
--to=rodrigo.vivi@intel.com \
--cc=anshuman.gupta@intel.com \
--cc=badal.nilawar@intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=karthik.poosa@intel.com \
--cc=mallesh.koujalagi@intel.com \
--cc=raag.jadav@intel.com \
--cc=ravi.kishore.koppuravuri@intel.com \
--cc=riana.tauro@intel.com \
--cc=sk.anirban@intel.com \
--cc=soham.purkait@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox