Re: [PATCH] tests/core_hotunplug: add *-with-load subtests exercising GPU workload during hotplug

Janusz Krzysztofik <[email protected]>
Newsgroups org.freedesktop.lists.igt-dev
Organization Intel Technology Poland sp. z o.o. - ul. Slowackiego 173, 80-298 Gdansk - KRS 101882 - NIP 957-07-52-316
Message-ID <[email protected]>
Hi Nitin,

Good job, however, I think we still need some improvements.

On Fri, 2026-08-21 at 12:28 +0530, Nitin Gote wrote:
> The six fd-holding hot* subtests keep a DRM fd open across unbind/unplug
> but leave the GPU idle, so the kernel hotplug path is never exercised
> against an active workload.
> 
> Add six new dedicated *-with-load subtests that run a GPU spinner across
> the unbind/unplug sequence so the device is removed while a workload is
> actively using it.
> 
> The workload helpers use an explicit chipset dispatch block in
> workload_start()/stop() so that other vendors can contribute
> support by adding an else-if branch. New subtests are gated on
> workload_available(), which currently covers DRIVER_XE and DRIVER_INTEL,
> and returns false (SKIP) on any other driver.
> 
> igt_spin_free() is avoided in workload_stop() since the device may be
> gone by then -- igt_spin_end() writes the stop condition via a userspace
> mmap and lets the DRM fd close reclaim kernel-side resources.
> 
> Validated on BMG and DG2 with a KASAN-enabled kernel:
> all six new subtests pass with no KASAN reports.
> 
> v4: Rebase to latest igt.
>     Also the failure seen with the new core_hotunplug "with-load"
>     IGT subtests is fixed by kmd patch:
>     https://patchwork.freedesktop.org/series/171989/
> 
> v3: Extend struct gpu_workload with a union of vendor-named members
>     to match the chipset dispatch in workload_start()/stop(). (Janusz)
> 
> v2: Pass the hotunplug priv struct to workload_start()/stop()
>     and dispatch via an explicit if/else-if chipset block to invite
>     other vendors to contribute. (Janusz)
> 
> Cc: Matthew Auld <[email protected]>
> Cc: Janusz Krzysztofik <[email protected]>
> Cc: Kamil Konieczny <[email protected]>
> Reviewed-by: Janusz Krzysztofik <[email protected]>
> Signed-off-by: Nitin Gote <[email protected]>
> ---
>  tests/core_hotunplug.c | 294 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 294 insertions(+)
> 
> diff --git a/tests/core_hotunplug.c b/tests/core_hotunplug.c
> index 33de65369..b11a4231b 100644
> --- a/tests/core_hotunplug.c
> +++ b/tests/core_hotunplug.c
> @@ -80,6 +80,34 @@
>   * SUBTEST: unplug-rescan
>   * Description: Check if a device believed to be closed can be cleanly
>   *		unplugged, then restored
> + *
> + * SUBTEST: hotunbind-rebind-with-load
> + * Description: Check if the driver can be cleanly unbound from an open device
> + *		with a background GPU workload in flight, then released and
> + *		rebound
> + *
> + * SUBTEST: hotunplug-rescan-with-load
> + * Description: Check if an open device with a background GPU workload in
> + *		flight can be cleanly unplugged, then released and restored
> + *
> + * SUBTEST: hotrebind-with-load
> + * Description: Check if the driver can be cleanly rebound to a device with a
> + *		still open hot unbound driver instance while a background GPU
> + *		workload is in flight
> + *
> + * SUBTEST: hotreplug-with-load
> + * Description: Check if a hot unplugged and still open device can be cleanly
> + *		restored while a background GPU workload is in flight
> + *
> + * SUBTEST: hotrebind-lateclose-with-load
> + * Description: Check if a hot unbound driver instance still open after hot
> + *		rebind with a background GPU workload in flight can be cleanly
> + *		released
> + *
> + * SUBTEST: hotreplug-lateclose-with-load
> + * Description: Check if an instance of a still open while hot replugged
> + *		device with a background GPU workload in flight can be cleanly
> + *		released
>   */
>  
>  IGT_TEST_DESCRIPTION("Examine behavior of a driver on device hot unplug");
> @@ -550,6 +578,56 @@ static void post_healthcheck(struct hotunplug *priv)
>  	cleanup(priv);
>  }
>  
> +/* GPU workload helpers */
> +
> +struct gpu_workload {
> +	union {
> +		struct {
> +			igt_spin_t *spin;
> +			uint64_t ahnd;
> +		} intel; /* Xe and i915 */
> +	};
> +};
> +
> +static bool workload_available(int chipset)
> +{
> +	return chipset == DRIVER_XE || chipset == DRIVER_INTEL;

NIT: I think a switch (chipset) { case DRIVER_XE: ... } construct, here 
and consequently below in workload_start/stop, could be more effective and 
more easily extendable with potential future additions from other vendors, 
but that can be refactored later, on a first such addition.

> +}
> +
> +static void workload_start(struct hotunplug *priv, int fd,
> +			   struct gpu_workload *w)
> +{
> +	if (priv->chipset == DRIVER_XE || priv->chipset == DRIVER_INTEL) {
> +		local_debug("%s\n", "starting GPU spinner");
> +		priv->failure = "GPU workload start failure!";
> +		w->intel.ahnd = intel_allocator_open(fd, 0, INTEL_ALLOCATOR_RELOC);
> +		w->intel.spin = igt_spin_new(fd, .ahnd = w->intel.ahnd);
> +		priv->failure = NULL;
> +	}
> +}
> +
> +static void workload_stop(struct hotunplug *priv, struct gpu_workload *w)
> +{
> +	if (priv->chipset == DRIVER_XE || priv->chipset == DRIVER_INTEL) {
> +		if (!w->intel.spin)
> +			return;

Since igt_fail() can be called from inside igt_spin_new(), workload_start()
can fail leaving an open ahnd but no spin.  We need to handle that case.

> +
> +		/*
> +		 * The device may be gone (unbind/unplug), so avoid
> +		 * igt_spin_free(): on Xe it would wait forever on a syncobj
> +		 * that never signals; on i915 it would touch the dead fd via
> +		 * gem_munmap()/gem_close(). Instead, end the spinner via a
> +		 * userspace mmap write and let the DRM fd close reclaim the
> +		 * kernel-side resources.
> +		 */
> +		local_debug("%s\n", "stopping GPU spinner");
> +		igt_spin_end(w->intel.spin);
> +		put_ahnd(w->intel.ahnd);
> +		w->intel.spin = NULL;
> +		w->intel.ahnd = 0;
> +	}
> +}
> +
>  /* Subtests */
>  
>  static void unbind_rebind(struct hotunplug *priv)
> @@ -664,6 +742,150 @@ static void hotreplug_lateclose(struct hotunplug *priv)
>  	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
>  }
>  
> +static void hotunbind_rebind_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot unbind");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	driver_unbind(priv, "hot ", 0);

Since igt_fail() can be called from driver_unbind(), as well as from other 
functions called between workload_start() and workload_stop() in other 
subtests below, we need to address those cases to be on the safe side.
We could e.g.:
- embed struct gpu_workload inside struct hotunplug,
- extend cleanup() (or recover()) function with another workload_stop().

Thanks,
Janusz


> +
> +	workload_stop(priv, &w);
> +
> +	priv->fd.drm = close_device(priv->fd.drm, "late ", "unbound ");
> +	igt_assert_eq(priv->fd.drm, -1);
> +
> +	driver_bind(priv, 0);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
> +static void hotunplug_rescan_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot unplug");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	device_unplug(priv, "hot ", 0);
> +
> +	workload_stop(priv, &w);
> +
> +	priv->fd.drm = close_device(priv->fd.drm, "late ", "removed ");
> +	igt_assert_eq(priv->fd.drm, -1);
> +
> +	bus_rescan(priv, 0);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
> +static void hotrebind_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot rebind");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	driver_unbind(priv, "hot ", 60);
> +
> +	driver_bind(priv, 0);
> +
> +	workload_stop(priv, &w);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
> +static void hotreplug_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot replug");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	device_unplug(priv, "hot ", 60);
> +
> +	bus_rescan(priv, 0);
> +
> +	workload_stop(priv, &w);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
> +static void hotrebind_lateclose_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot rebind");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	driver_unbind(priv, "hot ", 60);
> +
> +	driver_bind(priv, 0);
> +
> +	workload_stop(priv, &w);
> +
> +	priv->fd.drm = close_device(priv->fd.drm, "late ", "unbound ");
> +	igt_assert_eq(priv->fd.drm, -1);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
> +static void hotreplug_lateclose_with_load(struct hotunplug *priv)
> +{
> +	struct gpu_workload w = { 0 };
> +
> +	pre_check(priv);
> +
> +	igt_require_f(workload_available(priv->chipset),
> +		      "No GPU workload support for this driver\n");
> +
> +	priv->fd.drm = local_drm_open_driver(false, "", " for hot replug");
> +
> +	workload_start(priv, priv->fd.drm, &w);
> +
> +	device_unplug(priv, "hot ", 60);
> +
> +	bus_rescan(priv, 0);
> +
> +	workload_stop(priv, &w);
> +
> +	priv->fd.drm = close_device(priv->fd.drm, "late ", "removed ");
> +	igt_assert_eq(priv->fd.drm, -1);
> +
> +	igt_assert_f(healthcheck(priv, false), "%s\n", priv->failure);
> +}
> +
>  /* Main */
>  
>  int igt_main()
> @@ -802,6 +1024,78 @@ int igt_main()
>  			recover(&priv);
>  	}
>  
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if the driver can be cleanly unbound from an open device with a background GPU workload in flight, then released and rebound");
> +		igt_subtest("hotunbind-rebind-with-load")
> +			hotunbind_rebind_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if an open device with a background GPU workload in flight can be cleanly unplugged, then released and restored");
> +		igt_subtest("hotunplug-rescan-with-load")
> +			hotunplug_rescan_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if the driver can be cleanly rebound to a device with a still open hot unbound driver instance while a background GPU workload is in flight");
> +		igt_subtest("hotrebind-with-load")
> +			hotrebind_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if a hot unplugged and still open device can be cleanly restored while a background GPU workload is in flight");
> +		igt_subtest("hotreplug-with-load")
> +			hotreplug_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if a hot unbound driver instance still open after hot rebind with a background GPU workload in flight can be cleanly released");
> +		igt_subtest("hotrebind-lateclose-with-load")
> +			hotrebind_lateclose_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
> +	igt_fixture()
> +		post_healthcheck(&priv);
> +
> +	igt_subtest_group() {
> +		igt_describe("Check if an instance of a still open while hot replugged device with a background GPU workload in flight can be cleanly released");
> +		igt_subtest("hotreplug-lateclose-with-load")
> +			hotreplug_lateclose_with_load(&priv);
> +
> +		igt_fixture()
> +			recover(&priv);
> +	}
> +
>  	igt_fixture() {
>  		post_healthcheck(&priv);
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.