From: "Govindapillai, Vinod" <vinod.govindapillai@intel.com>
To: "igt-dev@lists.freedesktop.org" <igt-dev@lists.freedesktop.org>,
"Samala, Pranay" <pranay.samala@intel.com>
Cc: "Lattannavar, Sameer" <sameer.lattannavar@intel.com>,
"B S, Karthik" <karthik.b.s@intel.com>,
"Joshi, Kunal" <kunal.joshi@intel.com>
Subject: Re: [PATCH i-g-t 7/7] tests/chamelium/kms_chamelium_hpd: Add HPD from runtime suspended D3hot
Date: Wed, 16 Sep 2026 15:40:28 +0000 [thread overview]
Message-ID: <03523cc50d5f23dfbb0ec01d700c7692605e6cdf.camel@intel.com> (raw)
In-Reply-To: <20260907142259.750528-8-pranay.samala@intel.com>
Hi Pranay,
Couple of suggestions..
Should the test name reflect the PME - something like dp/hdmi-hpd-pme-
after-runtime-suspend-d3hot?
Also now /sys/bus/pci/devices/0000:00:02.0/power/wakeup is enabled to
run the above test? Do we need a test where it is disabled and see it
go back to to HPD polling.
BR
Vinod
On Mon, 2026-09-07 at 19:52 +0530, Pranay Samala wrote:
> Add dp-hpd-after-runtime-suspend-d3hot and
> hdmi-hpd-after-runtime-suspend-d3hot, covering a hotplug signalled by
> a
> PME while the device is runtime suspended in D3hot. The existing
> *-hpd-after-suspend subtests use system suspend instead.
>
> Checking only that the display comes back would pass whether or not
> the
> driver fell back to the ~10s connector poll, so the subtest runs two
> phases against the same suspended device.
>
> Phase 1 parks the device in D3hot with PME_En set and samples the D
> state from the PCI Power Management Control/Status register across a
> window longer than two poll periods, requiring that it never leaves
> D3hot. Any resume there means polling is still enabled.
>
> Phase 2 toggles the HPD from the chamelium and requires a prompt
> uevent,
> an updated connector status, and an increase in
> power/wakeup_active_count. The counter is what shows the device
> signalled its own resume rather than the host initiating one.
>
> Both phases report the D state, PME_Status and the counter delta on
> failure rather than asserting a cause, which separates a device that
> never signalled from one whose PME was not delivered and from one
> that
> resumed and dropped the HPD afterwards.
>
> Assisted-by: GitHub_Copilot:claude-opus-5
> Signed-off-by: Pranay Samala <pranay.samala@intel.com>
> ---
> tests/chamelium/kms_chamelium_hpd.c | 344
> ++++++++++++++++++++++++++++
> 1 file changed, 344 insertions(+)
>
> diff --git a/tests/chamelium/kms_chamelium_hpd.c
> b/tests/chamelium/kms_chamelium_hpd.c
> index bd8731c02..1135019c4 100644
> --- a/tests/chamelium/kms_chamelium_hpd.c
> +++ b/tests/chamelium/kms_chamelium_hpd.c
> @@ -33,6 +33,7 @@
> */
>
> #include "kms_chamelium_helper.h"
> +#include "igt_device.h"
>
> /**
> * SUBTEST: dp-hpd-fast
> @@ -114,6 +115,18 @@
> * Description: Toggle HPD during Suspend, check that uevents are
> sent and
> * connector status is updated
> *
> + * SUBTEST: dp-hpd-after-runtime-suspend-d3hot
> + * Description: Toggle HPD while runtime suspended in D3hot with PME
> signalling
> + * available, check that connector polling stayed
> disabled, that the
> + * device woke itself up and that uevents are sent and
> connector
> + * status is updated
> + *
> + * SUBTEST: hdmi-hpd-after-runtime-suspend-d3hot
> + * Description: Toggle HPD while runtime suspended in D3hot with PME
> signalling
> + * available, check that connector polling stayed
> disabled, that the
> + * device woke itself up and that uevents are sent and
> connector
> + * status is updated
> + *
> * SUBTEST: common-hpd-after-suspend
> * Description: Toggle HPD during suspend on all connectors, check
> that uevents
> * are sent and connector status is updated
> @@ -152,6 +165,33 @@
> #define HPD_TOGGLE_COUNT_DP_HDMI 15
> #define HPD_TOGGLE_COUNT_FAST 3
>
> +/*
> + * The KMS helper polls connectors every 10s, so watch for longer
> than two poll
> + * periods: a single delayed or coalesced poll then cannot make the
> test pass.
> + */
> +#define PME_POLL_PROOF_WINDOW_MS 25000
> +/*
> + * How often to sample the D state during that window. Config space
> reads do not
> + * resume a device suspended into D3hot, so this catches a poll
> induced resume
> + * that is too brief to stand out in the runtime PM accounting.
> + */
> +#define PME_D_STATE_SAMPLE_MS 100
> +/*
> + * If the device never leaves D3hot, all elapsed time is accounted
> to
> + * runtime_suspended_time and runtime_active_time does not move at
> all. Allow a
> + * small amount for one unrelated wakeup rather than requiring
> exactly zero,
> + * while staying well under what two or three poll induced resumes
> would cost.
> + */
> +#define PME_ACTIVE_TIME_SLACK_MS 100
> +/* Tolerance for accounting granularity and usleep() overshoot. */
> +#define PME_POLL_PROOF_SLACK_MS 500
> +/*
> + * A PME is delivered as an interrupt, so the wake is expected to be
> prompt. The
> + * whole point of the feature is that this is not the ~10s a poll
> would take.
> + */
> +#define PME_WAKE_LATENCY_MAX_MS 3000
> +#define PME_HPD_TOGGLE_DELAY_MS 1000
> +
> enum test_modeset_mode {
> TEST_MODESET_ON,
> TEST_MODESET_ON_OFF,
> @@ -383,6 +423,302 @@ static void
> test_suspend_resume_hpd_common(chamelium_data_t *data,
> igt_cleanup_uevents(mon);
> }
>
> +/*
> + * Name a D state read back from config space, for failure messages.
> PMCS cannot
> + * report D3cold - a device in D3cold is resumed by the read itself
> and answers
> + * as D0 - so an unknown state here means the read failed outright,
> i.e. the
> + * device has gone away.
> + */
> +static const char *pme_d_state_name(enum igt_acpi_d_state state)
> +{
> + switch (state) {
> + case IGT_ACPI_D0:
> + return "D0";
> + case IGT_ACPI_D1:
> + return "D1";
> + case IGT_ACPI_D2:
> + return "D2";
> + case IGT_ACPI_D3Hot:
> + return "D3hot";
> + case IGT_ACPI_D3Cold:
> + return "D3cold";
> + default:
> + return "an unreadable state";
> + }
> +}
> +
> +static void try_hpd_runtime_suspend_pme(chamelium_data_t *data,
> + struct chamelium_port *port,
> + struct pci_device *pci_dev,
> + struct udev_monitor *mon,
> + bool connected)
> +{
> + drmModeConnection target_state = connected ?
> DRM_MODE_DISCONNECTED :
> +
> DRM_MODE_CONNECTED;
> + int timeout = CHAMELIUM_HOTPLUG_TIMEOUT;
> + uint64_t susp_before, active_before, wakeups_before,
> wakeups_after;
> + uint64_t wakeups_at_toggle;
> + uint64_t susp_delta, active_delta;
> + struct timespec start, end;
> + int latency;
> +
> + igt_flush_uevents(mon);
> +
> + igt_assert_f(igt_wait_for_pm_status(IGT_RUNTIME_PM_STATUS_SU
> SPENDED),
> + "Device did not runtime suspend\n");
> +
> + /*
> + * Intel graphics devices support PME in D3hot but not in
> D3cold, and xe
> + * chooses the target state itself, so there is nothing to
> force from
> + * userspace. In practice an integrated device always lands
> in D3hot,
> + * since xe needs an ACPI _PR3 power resource on the
> upstream port before
> + * it considers D3cold at all. Check anyway: on a device
> that did drop to
> + * D3cold there is no PME to observe and everything below
> would only
> + * exercise the polling fallback, so skip.
> + *
> + * A device in D3cold answers this read as D0 rather than as
> D3cold, for
> + * the reason described above pme_d_state_name(), which
> fails the check
> + * just the same.
> + */
> + igt_require_f(igt_pm_pci_get_d_state(pci_dev) ==
> IGT_ACPI_D3Hot,
> + "Device is not suspended in D3hot, so there is
> no PME to "
> + "observe\n");
> +
> + /*
> + * PME_En is the direct sign that the feature engaged;
> without it, the test
> + * would only be measuring the polling fallback.
> + */
> + igt_assert_f(igt_pm_pci_pme_enabled(pci_dev),
> + "PME_En is not set after suspending into
> D3hot\n");
> +
> + /*
> + * Phase 1: ensure the device stays in D3hot while idle. If
> polling remains on,
> + * the KMS helper will resume it to probe connectors.
> + */
> + susp_before = igt_pm_get_runtime_suspended_time(pci_dev);
> + active_before = igt_pm_get_runtime_active_time(pci_dev);
> + wakeups_before = igt_pm_get_wakeup_active_count(pci_dev);
> +
> + igt_assert_eq(igt_gettime(&start), 0);
> + do {
> + enum igt_acpi_d_state d_state =
> igt_pm_pci_get_d_state(pci_dev);
> +
> + /*
> + * Distinguish self-woken resumes from host-driven
> ones. A self-wakeup bumps
> + * wakeup_active_count; a host-driven resume does
> not.
> + */
> + if (d_state != IGT_ACPI_D3Hot) {
> + bool self_woke;
> +
> + wakeups_after =
> igt_pm_get_wakeup_active_count(pci_dev);
> + self_woke = wakeups_after > wakeups_before;
> +
> + igt_assert_eq(igt_gettime(&end), 0);
> + igt_assert_f(false,
> + "Device left D3hot into %s
> after %.0fms of "
> + "a %dms idle window.\n"
> + "wakeup_active_count %" PRIu64
> " -> %" PRIu64
> + ", so the resume was
> %s.\n%s\n",
> + pme_d_state_name(d_state),
> + igt_time_elapsed(&start, &end)
> * 1000,
> + PME_POLL_PROOF_WINDOW_MS,
> + wakeups_before, wakeups_after,
> + self_woke ? "signalled by the
> device" :
> + "initiated by the
> host",
> + self_woke ?
> + "No hotplug was requested yet,
> so this is a "
> + "spurious or still pending PME
> rather than "
> + "the polling fallback." :
> + "The polling fallback resumes
> on a 10s period, "
> + "or 1s while a delayed event is
> pending, so "
> + "compare the elapsed time
> above: a wake at an "
> + "unrelated delay is some other
> client or "
> + "driver taking a display power
> reference "
> + "rather than connector
> polling.");
> + }
> +
> + usleep(PME_D_STATE_SAMPLE_MS * 1000);
> +
> + igt_assert_eq(igt_gettime(&end), 0);
> + } while (igt_time_elapsed(&start, &end) * 1000 <
> PME_POLL_PROOF_WINDOW_MS);
> +
> + susp_delta = igt_pm_get_runtime_suspended_time(pci_dev) -
> susp_before;
> + active_delta = igt_pm_get_runtime_active_time(pci_dev) -
> active_before;
> +
> + igt_info("Idle for %dms in D3hot: suspended +%" PRIu64 "ms,
> active +%" PRIu64 "ms\n",
> + PME_POLL_PROOF_WINDOW_MS, susp_delta,
> active_delta);
> +
> + /*
> + * Belt and braces on top of the D state sampling above,
> which covers
> + * resumes shorter than the sample interval: with the device
> parked in
> + * D3hot for the whole window, all of the elapsed time is
> accounted to
> + * runtime_suspended_time and runtime_active_time should
> barely move.
> + */
> + igt_assert_f(active_delta <= PME_ACTIVE_TIME_SLACK_MS,
> + "Device was active for %" PRIu64 "ms of a %dms
> idle window, "
> + "so it resumed for shorter than the %dms sample
> interval. "
> + "Something is still waking the device while
> idle and the "
> + "feature saves no power\n",
> + active_delta, PME_POLL_PROOF_WINDOW_MS,
> + PME_D_STATE_SAMPLE_MS);
> + igt_assert_lte(PME_POLL_PROOF_WINDOW_MS -
> PME_POLL_PROOF_SLACK_MS,
> + susp_delta);
> +
> + /*
> + * Phase 2: fire the HPD and check the device wakes up
> promptly. The
> + * toggle is scheduled over XMLRPC, which travels over the
> network and so
> + * does not disturb the GPU while we wait for the uevent.
> + *
> + * Take a fresh wakeup baseline rather than reusing the one
> from phase 1,
> + * which is a whole idle window old by now. Phase 1 only
> requires that
> + * the device stayed in D3hot, which a PME that resumed
> nothing at all
> + * would not violate, so the counter is not guaranteed to be
> unchanged.
> + */
> + wakeups_at_toggle = igt_pm_get_wakeup_active_count(pci_dev);
> +
> + chamelium_schedule_hpd_toggle(data->chamelium, port,
> + PME_HPD_TOGGLE_DELAY_MS,
> !connected);
> +
> + igt_assert_eq(igt_gettime(&start), 0);
> + if (!chamelium_wait_for_hotplug(mon, &timeout)) {
> + enum igt_acpi_d_state d_state =
> igt_pm_pci_get_d_state(pci_dev);
> + bool pending = igt_pm_pci_pme_status(pci_dev);
> + const char *cause;
> +
> + wakeups_after =
> igt_pm_get_wakeup_active_count(pci_dev);
> +
> + /*
> + * If the device stays in D3hot, the wake is from
> PME. If it returns to D0,
> + * the HPD was handled by the driver resume path.
> + */
> + if (d_state == IGT_ACPI_D3Hot)
> + cause = pending ?
> + "The device signalled a PME that was
> never "
> + "delivered, so the PME is not
> reaching the OS." :
> + "The device never signalled a PME,
> so the HPD "
> + "is not reaching the PME logic.";
> + else
> + cause = "The device resumed but sent no
> uevent, so the "
> + "HPD was dropped on the resume
> path.";
> +
> + igt_assert_f(false,
> + "No hotplug uevent %ds after an HPD
> toggle in D3hot.\n"
> + "Device is in %s, PME_Status is %s, "
> + "wakeup_active_count %" PRIu64 " -> %"
> PRIu64 ".\n%s\n",
> + CHAMELIUM_HOTPLUG_TIMEOUT,
> + pme_d_state_name(d_state),
> + pending ? "set" : "clear",
> + wakeups_at_toggle, wakeups_after,
> cause);
> + }
> + igt_assert_eq(igt_gettime(&end), 0);
> +
> + latency = igt_time_elapsed(&start, &end) * 1000 -
> + PME_HPD_TOGGLE_DELAY_MS;
> +
> + /*
> + * wakeup_active_count incrementing means the resume was
> signalled by the
> + * device itself, i.e. by a PME. Unlike PME_Status, which
> the PCI/PM core
> + * clears on the way back to D0, this counter survives the
> resume and so
> + * is not racy to sample here.
> + */
> + wakeups_after = igt_pm_get_wakeup_active_count(pci_dev);
> +
> + igt_info("HPD to uevent latency %dms, wakeup_active_count %"
> PRIu64 " -> %" PRIu64 "\n",
> + latency, wakeups_at_toggle, wakeups_after);
> +
> + igt_assert_f(wakeups_after > wakeups_at_toggle,
> + "power/wakeup_active_count did not increment,
> so the resume "
> + "was not triggered by a PME from the
> device\n");
> + igt_assert_lt(latency, PME_WAKE_LATENCY_MAX_MS);
> +
> + chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT);
> + igt_assert_eq(chamelium_reprobe_connector(&data->display,
> + data->chamelium,
> port),
> + target_state);
> +}
> +
> +/*
> + * Mirror of the driver's own HAS_PM_PME_SUPPORT(): only xe reports
> PME
> + * capability to the display code, and only from graphics IP 35.10
> on.
> + */
> +#define PME_HPD_MIN_GRAPHICS_VERX100 3500
> +
> +static bool pme_hpd_supported(int fd)
> +{
> + const struct intel_device_info *info;
> +
> + if (!is_xe_device(fd))
> + return false;
> +
> + info = intel_get_device_info(intel_get_drm_devid(fd));
> +
> + return info->graphics_ver * 100 + info->graphics_rel >=
> + PME_HPD_MIN_GRAPHICS_VERX100;
> +}
> +
> +static const char test_hpd_runtime_suspend_pme_desc[] =
> + "Toggle HPD while runtime suspended in D3hot with PME
> signalling "
> + "available, check that connector polling stayed disabled,
> that the "
> + "device woke itself up and that uevents are sent and
> connector status "
> + "is updated";
> +static void test_hpd_runtime_suspend_pme(chamelium_data_t *data,
> + struct chamelium_port
> *port)
> +{
> + struct pci_device *pci_dev;
> + struct udev_monitor *mon;
> +
> + pci_dev = igt_device_get_pci_device(data->drm_fd);
> +
> + /*
> + * Without driver support the device keeps HPD polling
> enabled and phase
> + * 1 would fail rather than skip, so gate on the platform
> before looking
> + * at the hardware capability at all.
> + */
> + igt_require_f(pme_hpd_supported(data->drm_fd),
> + "Driver does not use PME for HPD on this
> platform\n");
> +
> + igt_require_f(igt_pm_pci_pme_supported(pci_dev,
> IGT_ACPI_D3Hot),
> + "Device does not advertise PME support in
> D3hot\n");
> + igt_require_f(igt_pm_has_wakeup_support(pci_dev),
> + "Device does not expose power/wakeup\n");
> + igt_require(igt_setup_runtime_pm(data->drm_fd));
> + igt_require_hpd_storm_ctl(data->drm_fd);
> +
> + /*
> + * Need both: PME is only armed when device_may_wakeup() is
> true, and
> + * without wakeup support the PM core never records the PME
> wakeup.
> + */
> + igt_pm_set_wakeup_enabled(pci_dev, true);
> +
> + /*
> + * Save the poll setting so a previous test cannot leave it
> disabled and
> + * fake a pass. Polling must stay enabled; the test checks
> that the driver
> + * does not use it when PME is available.
> + */
> + igt_require(igt_pm_kms_poll_save());
> + igt_pm_kms_poll_set(true);
> +
> + /* Don't let our own toggles trip the storm detection into
> polling. */
> + igt_hpd_storm_set_threshold(data->drm_fd, 0);
> +
> + igt_modeset_disable_all_outputs(&data->display);
> +
> + mon = igt_watch_uevents();
> + chamelium_reset_state(&data->display, data->chamelium, port,
> + data->ports, data->port_count);
> +
> + /* Ports are left disconnected by the reset, so plug first.
> */
> + try_hpd_runtime_suspend_pme(data, port, pci_dev, mon,
> false);
> +
> + /* Now check we notice a disconnect signalled from D3hot
> too. */
> + try_hpd_runtime_suspend_pme(data, port, pci_dev, mon, true);
> +
> + igt_cleanup_uevents(mon);
> + igt_hpd_storm_reset(data->drm_fd);
> + igt_pm_kms_poll_restore();
> + igt_pm_restore_wakeup();
> +}
> +
> static const char test_hpd_without_ddc_desc[] =
> "Disable DDC on a VGA connector, check we still get a uevent
> on hotplug";
> static void test_hpd_without_ddc(chamelium_data_t *data,
> @@ -500,6 +836,10 @@ int igt_main()
> connector_subtest("dp-hpd-after-hibernate", DisplayPort,
> &data, test_suspend_resume_hpd,
> SUSPEND_STATE_DISK, SUSPEND_TEST_DEVICES);
>
> + igt_describe(test_hpd_runtime_suspend_pme_desc);
> + connector_subtest("dp-hpd-after-runtime-suspend-d3hot",
> DisplayPort,
> + &data, test_hpd_runtime_suspend_pme);
> +
> igt_describe(test_hpd_storm_detect_desc);
> connector_subtest("dp-hpd-storm", DisplayPort, &data,
> test_hpd_storm_detect,
> HPD_STORM_PULSE_INTERVAL_DP);
> @@ -538,6 +878,10 @@ int igt_main()
> connector_subtest("hdmi-hpd-after-hibernate", HDMIA, &data,
> test_suspend_resume_hpd,
> SUSPEND_STATE_DISK, SUSPEND_TEST_DEVICES);
>
> + igt_describe(test_hpd_runtime_suspend_pme_desc);
> + connector_subtest("hdmi-hpd-after-runtime-suspend-d3hot",
> HDMIA,
> + &data, test_hpd_runtime_suspend_pme);
> +
> igt_describe(test_hpd_storm_detect_desc);
> connector_subtest("hdmi-hpd-storm", HDMIA, &data,
> test_hpd_storm_detect,
> HPD_STORM_PULSE_INTERVAL_HDMI);
next prev parent reply other threads:[~2026-09-16 15:41 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-07 14:22 [PATCH i-g-t 0/7] Validate PM_PME signalling on display hotplug Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 1/7] lib/igt_pci: Add PCI Power Management capability register layout Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 2/7] lib/igt_pm: Add PCI PME capability and D state accessors Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 3/7] lib/igt_pm: Factor out power attribute path construction Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 4/7] lib/igt_pm: Add power/wakeup accessors Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 5/7] lib/igt_pm: Add power/wakeup_active_count accessor Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 6/7] lib/igt_pm: Add drm_kms_helper.poll save/restore helpers Pranay Samala
2026-09-07 14:22 ` [PATCH i-g-t 7/7] tests/chamelium/kms_chamelium_hpd: Add HPD from runtime suspended D3hot Pranay Samala
2026-09-16 15:40 ` Govindapillai, Vinod [this message]
2026-09-07 19:49 ` ✓ Xe.CI.BAT: success for Validate PM_PME signalling on display hotplug (rev2) Patchwork
2026-09-07 20:02 ` ✓ i915.CI.BAT: " Patchwork
2026-09-08 0:17 ` ✗ Xe.CI.FULL: failure " Patchwork
2026-09-08 6:28 ` ✗ 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=03523cc50d5f23dfbb0ec01d700c7692605e6cdf.camel@intel.com \
--to=vinod.govindapillai@intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=karthik.b.s@intel.com \
--cc=kunal.joshi@intel.com \
--cc=pranay.samala@intel.com \
--cc=sameer.lattannavar@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