[v11,1/1] tests: Add a new test for device hot unplug

Submitted by Janusz Krzysztofik on June 4, 2019, 8:12 a.m.

Details

Message ID 20190604081258.21340-2-janusz.krzysztofik@linux.intel.com
State New
Headers show
Series "tests: Add a new test for device hot unplug" ( rev: 3 ) in IGT - Trybot

Not browsing as part of any series.

Commit Message

Janusz Krzysztofik June 4, 2019, 8:12 a.m.
From: Janusz Krzysztofik <janusz.krzysztofik@intel.com>

There is a test which verifies unloading of i915 driver module but no test
exists that checks how a driver behaves when it gets unbound from a device
or when the device gets unplugged.  Provide such test using sysfs
interface.

Two minimalistic subtests - "unbind-rebind" and "unplug-rescan" - perform
desired operations on a DRM device which is beleived to be not in use.

A subtest named "drm_open-hotunplug" unplugs a DRM device while keeping
a file descriptor open.

Signed-off-by: Janusz Krzysztofik <janusz.krzysztofik@intel.com>
---
 tests/Makefile.sources |   1 +
 tests/core_hotunplug.c | 222 +++++++++++++++++++++++++++++++++++++++++
 tests/meson.build      |   1 +
 3 files changed, 224 insertions(+)
 create mode 100644 tests/core_hotunplug.c

Patch hide | download patch | download mbox

diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index ef3c03f2..1cdcf562 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -17,6 +17,7 @@  TESTS_progs = \
 	core_getclient \
 	core_getstats \
 	core_getversion \
+	core_hotunplug \
 	core_setmaster_vs_auth \
 	debugfs_test \
 	drm_import_export \
diff --git a/tests/core_hotunplug.c b/tests/core_hotunplug.c
new file mode 100644
index 00000000..d36a0572
--- /dev/null
+++ b/tests/core_hotunplug.c
@@ -0,0 +1,222 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright © 2019 Intel Corporation
+ */
+
+#include "igt.h"
+#include "igt_device.h"
+#include "igt_dummyload.h"
+#include "igt_kmod.h"
+#include "igt_sysfs.h"
+
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+struct hotunplug {
+	int chipset;
+	struct {
+		int drm;
+		int sysfs_dev;
+		int sysfs_bus;
+	} fd;
+};
+
+/* Helpers */
+
+static void prepare(struct hotunplug *priv)
+{
+	/* open the driver */
+	priv->fd.drm = __drm_open_driver(priv->chipset);
+	igt_assert(priv->fd.drm >= 0);
+
+	/* prepare for device unplug */
+	priv->fd.sysfs_dev = igt_sysfs_open(priv->fd.drm);
+	igt_assert(priv->fd.sysfs_dev >= 0);
+
+	/* prepare for bus rescan */
+	priv->fd.sysfs_bus = openat(priv->fd.sysfs_dev, "device/subsystem",
+				    O_DIRECTORY);
+	igt_assert(priv->fd.sysfs_bus >= 0);
+}
+
+/* Re-bind the driver to the device */
+static void driver_bind(int fd_sysfs_drv, const char *dev_bus_addr)
+{
+	igt_set_timeout(60, "Driver re-bind timeout!");
+	igt_sysfs_set(fd_sysfs_drv, "bind", dev_bus_addr);
+	igt_reset_timeout();
+
+	close(fd_sysfs_drv);
+}
+
+/* Unbind the driver from the device */
+static void driver_unbind(int fd_sysfs_drv, const char *dev_bus_addr)
+{
+	igt_set_timeout(60, "Driver unbind timeout!");
+	igt_sysfs_set(fd_sysfs_drv, "unbind", dev_bus_addr);
+	igt_reset_timeout();
+
+	/* don't close fd_sysfs_drv, it will be used for driver rebinding */
+}
+
+/* Re-discover the device by rescanning its bus */
+static void bus_rescan(int fd_sysfs_bus)
+{
+	igt_set_timeout(60, "Bus rescan timeout!");
+	igt_sysfs_set(fd_sysfs_bus, "rescan", "1");
+	igt_reset_timeout();
+
+	close(fd_sysfs_bus);
+}
+
+/* Remove (virtually unplug) the device from its bus */
+static void device_unplug(int fd_sysfs_dev)
+{
+	igt_set_timeout(60, "Device unplug timeout!");
+	igt_sysfs_set(fd_sysfs_dev, "device/remove", "1");
+	igt_reset_timeout();
+
+	close(fd_sysfs_dev);
+}
+
+/* Subtests */
+
+static void unbind_rebind(int chipset)
+{
+	struct hotunplug priv;
+	int fd_sysfs_drv, len;
+	char path[PATH_MAX];
+	const char *dev_bus_addr;
+
+	priv.chipset = chipset;
+	prepare(&priv);
+
+	/* sysfs_bus not needed for unbind/rebind */
+	close(priv.fd.sysfs_bus);
+
+	/* prepare for driver bind/unbind */
+	fd_sysfs_drv = openat(priv.fd.sysfs_dev, "device/driver", O_DIRECTORY);
+	igt_assert(fd_sysfs_drv >= 0);
+
+	len = readlinkat(priv.fd.sysfs_dev, "device", path, sizeof(path) - 1);
+	path[len] = '\0';
+	dev_bus_addr = strrchr(path, '/') + 1;
+
+	/* sysfs_dev no longer needed for unbind/rebind */
+	close(priv.fd.sysfs_dev);
+
+	igt_debug("closing device\n");
+	close(priv.fd.drm);
+
+	igt_debug("unbinding driver\n");
+	driver_unbind(fd_sysfs_drv, dev_bus_addr);
+
+	igt_debug("rebinding driver\n");
+	driver_bind(fd_sysfs_drv, dev_bus_addr);
+
+	igt_debug("reopening device\n");
+	priv.fd.drm = __drm_open_driver(chipset);
+	igt_assert(priv.fd.drm >= 0);
+
+	close(priv.fd.drm);
+}
+
+static void unplug_rescan(int chipset)
+{
+	struct hotunplug priv;
+
+	priv.chipset = chipset;
+	prepare(&priv);
+
+	igt_debug("closing device\n");
+	close(priv.fd.drm);
+
+	igt_debug("unplugging device\n");
+	device_unplug(priv.fd.sysfs_dev);
+
+	igt_debug("recovering device\n");
+	bus_rescan(priv.fd.sysfs_bus);
+
+	igt_debug("reopening driver\n");
+	priv.fd.drm = __drm_open_driver(chipset);
+	igt_assert(priv.fd.drm >= 0);
+
+	close(priv.fd.drm);
+}
+
+static void drm_open_hotunplug(int chipset)
+{
+	struct hotunplug priv;
+
+	priv.chipset = chipset;
+	prepare(&priv);
+
+	igt_debug("unplugging device\n");
+	device_unplug(priv.fd.sysfs_dev);
+
+	igt_debug("recovering device\n");
+	bus_rescan(priv.fd.sysfs_bus);
+
+	igt_debug("closing device\n");
+	close(priv.fd.drm);
+
+	igt_debug("reopening driver\n");
+	priv.fd.drm = __drm_open_driver(chipset);
+	igt_assert(priv.fd.drm >= 0);
+
+	close(priv.fd.drm);
+}
+
+/* Main */
+
+igt_main {
+	int chipset;
+	char *module;
+
+	igt_fixture {
+		char path[PATH_MAX];
+		int fd_drm, fd_sysfs_dev, len;
+
+		/**
+		 * Since some subtests depend on successful unload of a driver
+		 * module, don't use drm_open_driver() as it keeps a device file
+		 * descriptor open for exit handler use and that effectively
+		 * prevents the module from being unloaded.
+		 */
+		fd_drm = __drm_open_driver(DRIVER_ANY);
+		igt_assert(fd_drm >= 0);
+
+		if (is_i915_device(fd_drm)) {
+			chipset = DRIVER_INTEL;
+			module = strdup("i915");
+		} else {
+			chipset = DRIVER_ANY;
+
+			/* Capture module name to be unloaded */
+			fd_sysfs_dev = igt_sysfs_open(fd_drm);
+			len = readlinkat(fd_sysfs_dev, "device/driver/module",
+					 path, sizeof(path) - 1);
+			close(fd_sysfs_dev);
+			path[len] = '\0';
+			module = strdup(strrchr(path, '/') + 1);
+		}
+		close(fd_drm);
+
+		igt_info("Running the test on driver \"%s\", chipset mask %#0x\n",
+			 module, chipset);
+	}
+
+	igt_subtest("unbind-rebind")
+		unbind_rebind(chipset);
+
+	igt_subtest("unplug-rescan")
+		unplug_rescan(chipset);
+
+	igt_subtest("drm_open-hotunplug")
+		drm_open_hotunplug(chipset);
+
+	igt_fixture {
+		free(module);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index 806766e5..f0531a4e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -3,6 +3,7 @@  test_progs = [
 	'core_getclient',
 	'core_getstats',
 	'core_getversion',
+	'core_hotunplug',
 	'core_setmaster_vs_auth',
 	'debugfs_test',
 	'drm_import_export',