@@ -17,6 +17,7 @@ TESTS_progs = \
core_getclient \
core_getstats \
core_getversion \
+ core_hotunplug \
core_setmaster_vs_auth \
debugfs_test \
drm_import_export \
new file mode 100644
@@ -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);
+ }
+}
@@ -3,6 +3,7 @@ test_progs = [
'core_getclient',
'core_getstats',
'core_getversion',
+ 'core_hotunplug',
'core_setmaster_vs_auth',
'debugfs_test',
'drm_import_export',