[igt-dev] [PATCH i-g-t v2 1/1] Introduce new method of device selection

Zbigniew Kempczyński zbigniew.kempczynski at intel.com
Fri Jul 19 09:50:14 UTC 2019


Change adds device selection based on implemented filters.
Different filters can be added to address different device
selection possibilities.

New device selection uses --device filter or IGT_DEVICE environent
variable. Selection of many devices can be done by using --device
argument multiple times. As IGT_DEVICE enviroment can be set
once ';' is recognized as filter separator.

Tool 'lsgpu' which uses device scanning feature was added.

---
v2: removing module management, keeping only simple device pci/
    platform scanning

Signed-off-by: Zbigniew Kempczyński <zbigniew.kempczynski at intel.com>
Cc: Arkadiusz Hiler <arkadiusz.hiler at intel.com>
Cc: Daniel Vetter <daniel at ffwll.ch>
Cc: Petri Latvala <petri.latvala at intel.com>
---
 docs/multi-device-selection.txt |  108 +++
 lib/Makefile.sources            |    2 +
 lib/drmtest.c                   |  151 ++++-
 lib/drmtest.h                   |    9 +
 lib/igt_core.c                  |   13 +
 lib/igt_device_scan.c           | 1084 +++++++++++++++++++++++++++++++
 lib/igt_device_scan.h           |   82 +++
 lib/meson.build                 |    1 +
 tools/Makefile.sources          |    1 +
 tools/lsgpu.c                   |  221 +++++++
 tools/meson.build               |    1 +
 11 files changed, 1671 insertions(+), 2 deletions(-)
 create mode 100644 docs/multi-device-selection.txt
 create mode 100644 lib/igt_device_scan.c
 create mode 100644 lib/igt_device_scan.h
 create mode 100644 tools/lsgpu.c

diff --git a/docs/multi-device-selection.txt b/docs/multi-device-selection.txt
new file mode 100644
index 00000000..1e8bc7b9
--- /dev/null
+++ b/docs/multi-device-selection.txt
@@ -0,0 +1,108 @@
+Multi-device scanning and selection
+-----------------------------------
+
+1. Scanning
+
+Device selection is build around scanning buses using udev (PCI and platform),
+gathering information about matching devices and using filters on discovered
+data.
+
+PCI scanning is easy and gives all information regarding devices (module doesn't
+need to be loaded). Problems occurs when platform devices are taken into
+account. Some of them have appropriate entries in device tree (like
+Raspberry Pi vc4 driver), so displaying them without loaded module is possible.
+Such devices expose OF_COMPATIBLE_X properties which are matched against
+appropriate driver. Some devices are registered inside platform tree at driver
+loading time so discovering such devices is not possible unless driver is loaded.
+
+
+2. Filtering
+
+After scanning all gathered information are stored in devices array. Filtering
+means creating a view that matches filter on devices array. Filters defined
+on non-discoverable devices loads module (if not already loaded) and execute
+device discovery again.
+
+Using 'lsgpu' tool we can get list of defined filter types ('-l' switch):
+...
+pci       pci:[vendor=%04x/name][,device=%04x][,card=%d]
+	  vendor is hex number or name
+...
+
+pci is filter name, it calls filter on devices array to select only PCI
+devices. For example:
+
+pci:vendor=8086
+find first pci card on the bus with 8086 PCI device ID
+
+pci:vendor=intel
+same as above but uses vendor name (see lsgpu -v)
+
+pci:vendor=8086,card=0
+same as above, card=0 is default
+
+pci:vendor=intel,device=1234,card=1
+finds second Intel card which device ID is 0x1234
+
+
+3. lsgpu
+
+To play with devices 'lsgpu' tool was written. It is mostly wrapper to igt
+devices scan code (igt_device_scan.c).
+
+If run without arguments it displays all recognized cards.
+Usage examples:
+
+# lsgpu -d
+displays devices in 'detail' mode, with all properties and attributes. Some
+attributes are skipped because acquiring value can take even seconds (there's
+special blacklist hash table inside the code)
+
+# lsgpu -v
+displays recognized vendors
+
+# lsgpu -l
+displays defined filter types
+
+# lsgpu --device 'pci:card=0;vgem:card=0;vkms:card=0'
+and
+# lsgpu --device 'pci:card=0' --device 'vgem:card=0' --device 'vkms:card=0'
+and
+# export IGT_DEVICE='pci:card=0;vgem:card=0;vkms:card=0'
+# lsgpu
+matches multiple cards. IGT_DEVICE uses ';' separated filter syntax, it is also
+permitted in --device (function splits argument against ';').
+
+
+4. Compatibility with current device open API
+
+Practically all IGT tests use:
+int __drm_open_driver(int chipset);
+int __drm_open_driver_render(int chipset);
+
+Above functions were extended to use --device / env IGT_DEVICE filters
+(if given during IGT test run). If more than single device were passed
+first matching filter against chipset is selected. This allows test like
+prime_vgem to work, ex:
+
+# ./prime_vgem --r basic-read --device 'pci:card=0' --device 'vgem:card=0'
+# ./prime_vgem --r basic-read --device 'vgem:card=0' --device 'pci:card=0'
+
+
+5. New device selection API
+
+Following prototypes define new API:
+
+/* Multi device API */
+int __drm_open_card_with_nth_filter(int num, int chipset);
+int __drm_open_render_with_nth_filter(int num, int chipset);
+
+When test require DRIVER_INTEL it has to be sure that user didn't passed
+another device (vc4 for example). So if user passed --device vc4:card=0
+this open will fail.
+
+struct igt_device_card;
+int __drm_open_card(struct igt_device_card *card);
+int __drm_open_render(struct igt_device_card *card);
+
+These functions allows to open device regardless chip requirement.
diff --git a/lib/Makefile.sources b/lib/Makefile.sources
index e16de86e..c383a817 100644
--- a/lib/Makefile.sources
+++ b/lib/Makefile.sources
@@ -25,6 +25,8 @@ lib_source_list =	 	\
 	igt_debugfs.h		\
 	igt_device.c		\
 	igt_device.h		\
+	igt_device_scan.c	\
+	igt_device_scan.h	\
 	igt_aux.c		\
 	igt_aux.h		\
 	igt_color_encoding.c	\
diff --git a/lib/drmtest.c b/lib/drmtest.c
index c379a7b7..434066ee 100644
--- a/lib/drmtest.c
+++ b/lib/drmtest.c
@@ -55,6 +55,7 @@
 #include "igt_gt.h"
 #include "igt_kmod.h"
 #include "igt_sysfs.h"
+#include "igt_device_scan.h"
 #include "version.h"
 #include "config.h"
 #include "intel_reg.h"
@@ -289,25 +290,171 @@ static int __open_driver(const char *base, int offset, unsigned int chipset)
 	return __search_and_open(base, offset, chipset);
 }
 
+static int __open_driver_exact(const char *name, unsigned int chipset)
+{
+	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+	int fd;
+
+	fd = open_device(name, chipset);
+	if (fd != -1)
+		return fd;
+
+	pthread_mutex_lock(&mutex);
+	for (const struct module *m = modules; m->module; m++) {
+		if (chipset & m->bit) {
+			if (m->modprobe)
+				m->modprobe(m->module);
+			else
+				modprobe(m->module);
+		}
+	}
+	pthread_mutex_unlock(&mutex);
+
+	return open_device(name, chipset);
+}
+
+/**
+ * __find_card_with_chipset
+ * @chipset: chipset for compare with card match
+ * @card: pointer where card information will be stored
+ *
+ * For compatibility mode when multiple --device argument were passed
+ * this function tries to be smart enough to handle tests which opens
+ * more than single device. It iterates over filter list and
+ * compares chipset to card chipset for filter matched.
+ *
+ * Returns:
+ * True if card according to filters added and chipset was found,
+ * false othwerwise.
+ */
+static bool __find_card_with_chipset(int chipset, struct igt_device_card *card)
+{
+	int i, n = igt_device_filter_count();
+	const char *filter;
+	bool match;
+
+	for (i = 0; i < n; i++) {
+		filter = igt_device_filter_get(i);
+		match = igt_device_card_match(filter, card);
+		if (match && (card->chipset == chipset || chipset == DRIVER_ANY))
+			return true;
+	}
+
+	return false;
+}
+
 /**
  * __drm_open_driver:
  * @chipset: OR'd flags for each chipset to search, eg. #DRIVER_INTEL
  *
- * Open the first DRM device we can find, searching up to 16 device nodes
+ * Function opens device with following order:
+ * 1. when --device arguments are present device scanning will be executed,
+ * then matching considers all device which matches filter argument will be selected.
+ * 2. compatibility mode - open the first DRM device we can find,
+ * searching up to 16 device nodes.
  *
  * Returns:
  * An open DRM fd or -1 on error
  */
 int __drm_open_driver(int chipset)
 {
+	int n = igt_device_filter_count();
+
+	if (n) {
+		bool found;
+		struct igt_device_card card;
+
+		found = __find_card_with_chipset(chipset, &card);
+		if (!found || !strlen(card.card))
+			return -1;
+
+		return __open_driver_exact(card.card, chipset);
+	}
+
 	return __open_driver("/dev/dri/card", 0, chipset);
 }
 
-static int __drm_open_driver_render(int chipset)
+int __drm_open_driver_render(int chipset)
 {
+	int n = igt_device_filter_count();
+
+	if (n) {
+		bool found;
+		struct igt_device_card card;
+
+		found = __find_card_with_chipset(chipset, &card);
+		if (!found || !strlen(card.render))
+			return -1;
+
+		return __open_driver_exact(card.render, chipset);
+	}
+
 	return __open_driver("/dev/dri/renderD", 128, chipset);
 }
 
+static int __drm_open_with_nth_filter(int num, int chipset, bool open_render)
+{
+	struct igt_device_card card;
+	const char *filter, *devname;
+	bool match;
+	int n = igt_device_filter_count();
+
+	if (!n || num < 0 || num >= n) {
+		igt_warn("No device filter num == %d passed\n", num);
+		return -1;
+	}
+
+	filter = igt_device_filter_get(num);
+	match = igt_device_card_match(filter, &card);
+	if (!match) {
+		igt_warn("No device match filter: %s\n", filter);
+		return -1;
+	}
+
+	if (chipset != card.chipset) {
+		igt_warn("Filtered device doesn't match chipset (%d != %d)\n",
+			 chipset, card.chipset);
+		return -1;
+	}
+	if (!strlen(card.card))
+		return -1;
+
+	devname = open_render ? card.render : card.card;
+	if (!strlen(devname)) {
+		igt_warn("No %s node matching filter: %s\n",
+			 open_render ? "render" : "card", filter);
+		return -1;
+	}
+	return __open_driver_exact(devname, card.chipset);
+}
+
+int __drm_open_card_with_nth_filter(int num, int chipset)
+{
+	return __drm_open_with_nth_filter(num, chipset, false);
+}
+
+int __drm_open_render_with_nth_filter(int num, int chipset)
+{
+	return __drm_open_with_nth_filter(num, chipset, true);
+}
+
+int __drm_open_card(struct igt_device_card *card)
+{
+	if (!card || !strlen(card->card))
+		return -1;
+
+	return __open_driver_exact(card->card, card->chipset);
+}
+
+int __drm_open_render(struct igt_device_card *card)
+{
+	if (!card || !strlen(card->render))
+		return -1;
+
+	return __open_driver_exact(card->render, card->chipset);
+}
+
+
 static int at_exit_drm_fd = -1;
 static int at_exit_drm_render_fd = -1;
 
diff --git a/lib/drmtest.h b/lib/drmtest.h
index 614f57e6..4adb9908 100644
--- a/lib/drmtest.h
+++ b/lib/drmtest.h
@@ -50,6 +50,7 @@
 #define DRIVER_AMDGPU	(1 << 3)
 #define DRIVER_V3D	(1 << 4)
 #define DRIVER_PANFROST	(1 << 5)
+
 /*
  * Exclude DRVER_VGEM from DRIVER_ANY since if you run on a system
  * with vgem as well as a supported driver, you can end up with a
@@ -81,6 +82,14 @@ int drm_open_driver(int chipset);
 int drm_open_driver_master(int chipset);
 int drm_open_driver_render(int chipset);
 int __drm_open_driver(int chipset);
+int __drm_open_driver_render(int chipset);
+
+/* Multi device API */
+int __drm_open_card_with_nth_filter(int num, int chipset);
+int __drm_open_render_with_nth_filter(int num, int chipset);
+struct igt_device_card;
+int __drm_open_card(struct igt_device_card *card);
+int __drm_open_render(struct igt_device_card *card);
 
 void gem_quiescent_gpu(int fd);
 
diff --git a/lib/igt_core.c b/lib/igt_core.c
index 1cbb09f9..9b851175 100644
--- a/lib/igt_core.c
+++ b/lib/igt_core.c
@@ -71,6 +71,7 @@
 #include "igt_sysrq.h"
 #include "igt_rc.h"
 #include "igt_list.h"
+#include "igt_device_scan.h"
 
 #define UNW_LOCAL_ONLY
 #include <libunwind.h>
@@ -304,6 +305,7 @@ enum {
 	OPT_DEBUG,
 	OPT_INTERACTIVE_DEBUG,
 	OPT_SKIP_CRC,
+	OPT_DEVICE,
 	OPT_HELP = 'h'
 };
 
@@ -624,6 +626,7 @@ static void print_usage(const char *help_str, bool output_on_stderr)
 		   "  --skip-crc-compare\n"
 		   "  --help-description\n"
 		   "  --describe\n"
+		   "  --device filter\n"
 		   "  --help|-h\n");
 	if (help_str)
 		fprintf(f, "%s\n", help_str);
@@ -725,6 +728,11 @@ static void common_init_env(void)
 	if (env) {
 		__set_forced_driver(env);
 	}
+
+	env = getenv("IGT_DEVICE");
+	if (env) {
+		igt_device_filter_add(env);
+	}
 }
 
 static int common_init(int *argc, char **argv,
@@ -743,6 +751,7 @@ static int common_init(int *argc, char **argv,
 		{"debug",             optional_argument, NULL, OPT_DEBUG},
 		{"interactive-debug", optional_argument, NULL, OPT_INTERACTIVE_DEBUG},
 		{"skip-crc-compare",  no_argument,       NULL, OPT_SKIP_CRC},
+		{"device",            required_argument, NULL, OPT_DEVICE},
 		{"help",              no_argument,       NULL, OPT_HELP},
 		{0, 0, 0, 0}
 	};
@@ -865,6 +874,10 @@ static int common_init(int *argc, char **argv,
 		case OPT_SKIP_CRC:
 			igt_skip_crc_compare = true;
 			goto out;
+		case OPT_DEVICE:
+			assert(optarg);
+			igt_device_filter_add(optarg);
+			break;
 		case OPT_HELP:
 			print_usage(help_str, false);
 			ret = -1;
diff --git a/lib/igt_device_scan.c b/lib/igt_device_scan.c
new file mode 100644
index 00000000..5e31469a
--- /dev/null
+++ b/lib/igt_device_scan.c
@@ -0,0 +1,1084 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "igt.h"
+#include "igt_sysfs.h"
+#include "igt_device.h"
+#include "igt_device_scan.h"
+#include <glib.h>
+#include <libudev.h>
+#include <linux/limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+/**
+ * SECTION:igt_device_scan
+ * @short_description: PCI and platform devices scan
+ * @title: Device scan
+ * @include: igt.h
+ *
+ * Device discovery uses udev functionality for scanning different subsystems.
+ * This code uses scanning 'pci' and 'platform' subsystems instead of direct
+ * iterating over 'drm' subsystem.
+*/
+
+//#define DEBUG_DEVICE_SCAN
+#ifdef DEBUG_DEVICE_SCAN
+#define DBG(...) \
+{ \
+	struct timeval tm; \
+	gettimeofday(&tm, NULL); \
+	printf("%10ld.%03ld: ", tm.tv_sec, tm.tv_usec); \
+	printf(__VA_ARGS__); \
+	}
+
+#else
+#define DBG(...) {}
+#endif
+
+#define IGT_DRM_PATH "/dev/dri"
+
+/* There're two possibilities to get drm devices.
+ * First is to scan 'drm' subsystem directly. It's easy but no additional
+ * metadata is easy to get (like vendor etc.).
+ * Second is to scan 'pci' and 'platform' subsystem with apropriate filters
+ * for devices we're interested of. For PCI devices it is PCI_CLASS attribute,
+ * for platform devices this is a little bit chaotic and depends on device
+ * vendor.
+*/
+
+enum igt_devices_scan_type {
+	IGT_SCAN_PCI       = 1,
+	IGT_SCAN_PLATFORM  = 2,
+};
+
+static GHashTable *sysattrs_blacklist_ht;  //sysattrs we don't want to read
+static GHashTable *gpu_vendor_ht;          //search id -> vendor_spec mapping
+static GHashTable *filter_definition_ht;   //supported filters (pci=..., etc.)
+
+/* Generic name->value struct */
+struct name_value {
+	const char *name;
+	gpointer *value;
+};
+
+/* Vendor specific data */
+struct vendor_spec {
+	enum igt_device_type dev_type;
+	const char *vendor;
+	const char *match;
+	int chipset;
+};
+
+/* Single igt_device found in PCI / platform subsystems */
+struct igt_device {
+	char *pretty_name;
+	struct vendor_spec *vs;
+	GHashTable *props_ht;
+	GHashTable *attrs_ht;
+	char *syspath;
+	char *drvpath;
+	char *drm_card_path;
+	char *drm_render_path;
+	char *vendor;
+	char *device;
+	enum igt_device_type dev_type;
+};
+
+/* Scanned devices struct */
+struct igt_devices {
+	GPtrArray *devs;		//all gpu devices array
+	GPtrArray *view;		//filtered view
+	bool devs_scanned;
+};
+
+/* Scanned devices holder */
+static struct igt_devices igt_devs;
+
+/* List of attribute key->value applied as udev filter
+ * for enumeration for PCI devices */
+struct name_value pci_udev_filters_attr_list[] = {
+	{ "PCI_CLASS", (gpointer) "30000" },
+	{ "PCI_CLASS", (gpointer) "30100" },
+	{ "PCI_CLASS", (gpointer) "30200" },
+	{ "PCI_CLASS", (gpointer) "38000" },
+	{ NULL, },
+};
+
+/* List of attribute key->value applied as udev filter
+ * for enumeration for platform devices */
+struct name_value platform_udev_filters_attr_list[] = {
+	{ "MODALIAS", (gpointer) "platform:vgem" },
+	{ "MODALIAS", (gpointer) "platform:vkms" },
+	{ NULL, },
+};
+
+static struct vendor_spec v_intel  = { .dev_type = DEV_PCI,
+				       .vendor = "Intel",
+				       .chipset = DRIVER_INTEL
+				     };
+static struct vendor_spec v_amd    = { .dev_type = DEV_PCI,
+				       .vendor = "AMD",
+				       .chipset = DRIVER_AMDGPU
+				     };
+static struct vendor_spec v_vgem   = { .dev_type = DEV_PLATFORM,
+				       .vendor = "Virtual-GEM",
+				       .chipset = DRIVER_VGEM
+				     };
+static struct vendor_spec v_vc4   = { .dev_type = DEV_PLATFORM,
+				       .vendor = "Broadcom",
+				       .chipset = DRIVER_VC4
+				     };
+
+/* Mapping vendor id => vendor_spec should be unique (vendor string matching
+ * is written around this).
+ *
+ * Keys must be defined as follows:
+ * PCI devices: PCI_SLOT_ID -> vendor_spec.
+*/
+struct name_value gpu_vendor_list[] = {
+	{ "8086", (gpointer) &v_intel },
+	{ "1002", (gpointer) &v_amd },
+	{ NULL, },
+};
+
+/* Generic hash table fill function, requires name / value ptrs array */
+static void fill_ht(GHashTable **ht, struct name_value *data)
+{
+	if (*ht)
+		return;
+
+	*ht = g_hash_table_new(g_str_hash, g_str_equal);
+	igt_assert(*ht);
+
+	while (data->name) {
+		g_hash_table_insert(*ht,
+				    (gpointer) data->name,
+				    data->value);
+		data++;
+	}
+}
+
+#define get_vendor_spec(prop) \
+	g_hash_table_lookup(gpu_vendor_ht, prop)
+
+/* Go through whole vendor hash table and compare against vendor field.
+ * Used mostly with vendor=... filter parameter when PCI id is not matched.
+*/
+static bool is_pci_vendor_name(const char *name)
+{
+	bool ret = false;
+	GList *keys = g_hash_table_get_keys(gpu_vendor_ht);
+
+	if (!name)
+		return false;
+
+	while (keys) {
+		char *k = (char *) keys->data;
+		struct vendor_spec *vs = g_hash_table_lookup(gpu_vendor_ht, k);
+		keys = g_list_next(keys);
+
+		if (vs->dev_type != DEV_PCI)
+			continue;
+
+		if (!strcasecmp(name, vs->vendor)) {
+			ret = true;
+			break;
+		}
+	}
+	g_list_free(keys);
+
+	return ret;
+}
+
+/* Reading sysattr values can take time (even seconds),
+ * we want to avoid reading such keys.
+*/
+static void populate_blacklist_keys(void)
+{
+	const char *keys[] = { "config", "modalias",
+			       "resource",
+			       "resource0", "resource1", "resource2",
+			       "resource3", "resource4", "resource5",
+			       "resource0_wc", "resource1_wc", "resource2_wc",
+			       "resource3_wc", "resource4_wc", "resource5_wc",
+			       "driver",
+			       "uevent", NULL};
+	const char *key;
+	int i = 0;
+
+	if (sysattrs_blacklist_ht)
+		return;
+
+	sysattrs_blacklist_ht = g_hash_table_new(g_str_hash, g_str_equal);
+	igt_assert(sysattrs_blacklist_ht);
+
+	while ((key = keys[i++]))
+		g_hash_table_add(sysattrs_blacklist_ht, (gpointer) key);
+}
+
+#define is_on_blacklist(key) \
+	g_hash_table_contains(sysattrs_blacklist_ht, key)
+
+static struct igt_device *igt_device_new(void)
+{
+	struct igt_device *dev;
+	dev = calloc(1, sizeof(struct igt_device));
+	if (!dev)
+		return NULL;
+
+	dev->attrs_ht = g_hash_table_new_full(g_str_hash, g_str_equal,
+					      free, free);
+	dev->props_ht = g_hash_table_new_full(g_str_hash, g_str_equal,
+					      free, free);
+
+	if (dev->attrs_ht && dev->props_ht)
+		return dev;
+
+	return NULL;
+}
+
+static void igt_device_add_prop(struct igt_device *dev,
+				const char *key, const char *value)
+{
+	if (!key || !value)
+		return;
+
+	g_hash_table_insert(dev->props_ht, strdup(key), strdup(value));
+}
+
+static void igt_device_add_attr(struct igt_device *dev,
+				const char *key, const char *value)
+{
+	const char *v = value;
+
+	if (!key)
+		return;
+
+	/* It's possible we have symlink at key filename, but udev
+	 * library resolves only few of them */
+	if (!v) {
+		struct stat st;
+		char path[PATH_MAX];
+		char linkto[PATH_MAX];
+		int len;
+
+		snprintf(path, sizeof(path), "%s/%s", dev->syspath, key);
+		if (lstat(path, &st) != 0)
+			return;
+
+		len = readlink(path, linkto, sizeof(linkto));
+		if (len <= 0 || len == (ssize_t) sizeof(linkto))
+			return;
+		linkto[len] = '\0';
+		v = strrchr(linkto, '/');
+		if (v == NULL)
+			return;
+		v++;
+	}
+
+	g_hash_table_insert(dev->attrs_ht, strdup(key), strdup(v));
+}
+
+/* Iterate over udev properties list and rewrite it to igt_device properties
+ * hash table for instant access.
+ */
+static void get_props(struct udev_device *dev, struct igt_device *idev)
+{
+	struct udev_list_entry *entry;
+
+	entry = udev_device_get_properties_list_entry(dev);
+	while (entry) {
+		const char *name = udev_list_entry_get_name(entry);
+		const char *value = udev_list_entry_get_value(entry);
+		igt_device_add_prop(idev, name, value);
+		entry = udev_list_entry_get_next(entry);
+		DBG("prop: %s, val: %s\n", name, value);
+	}
+}
+
+/* Same as get_props(), but rewrites sysattrs. Resolved symbolic links
+ * not handled by udev get_sysattr_value().
+ * Function skips sysattrs from blacklist ht (acquiring some values can take
+ * seconds).
+ */
+static void get_attrs(struct udev_device *dev, struct igt_device *idev)
+{
+	struct udev_list_entry *entry;
+
+	entry = udev_device_get_sysattr_list_entry(dev);
+	while (entry) {
+		const char *key = udev_list_entry_get_name(entry);
+		const char *value;
+
+		if (is_on_blacklist(key)) {
+			entry = udev_list_entry_get_next(entry);
+			continue;
+		}
+
+		value = udev_device_get_sysattr_value(dev, key);
+		igt_device_add_attr(idev, key, value);
+		entry = udev_list_entry_get_next(entry);
+		DBG("attr: %s, val: %s\n", key, value);
+	}
+}
+
+#define get_prop(dev, prop) (char *) g_hash_table_lookup(dev->props_ht, prop)
+#define get_attr(dev, attr) (char *) g_hash_table_lookup(dev->attrs_ht, attr)
+#define get_prop_pci_id(dev)    get_prop(dev, "PCI_ID")
+#define get_prop_pci_slot(dev)  get_prop(dev, "PCI_SLOT_NAME")
+#define get_prop_devpath(dev)   get_prop(dev, "DEVPATH")
+#define get_prop_driver(dev)    get_prop(dev, "DRIVER")
+#define get_prop_modalias(dev)  get_prop(dev, "MODALIAS")
+#define get_prop_of_compatible_0(dev) get_prop(dev, "OF_COMPATIBLE_0")
+
+/* Gets PCI_ID property, splits to xxxx:yyyy and stores
+ * xxxx to dev->vendor and yyyy to dev->device for
+ * faster access.
+ */
+static void set_vendor_device(struct igt_device *dev)
+{
+	const char *pci_id = get_prop_pci_id(dev);
+	if (!pci_id || strlen(pci_id) != 9)
+		return;
+	dev->vendor = strndup(pci_id, 4);
+	dev->device = strndup(pci_id + 5, 4);
+}
+
+/* Find appropriate /dev/dri/cardX and /dev/dri/renderDX nodes
+ * for igt_device.
+ */
+static void find_drm_paths(struct igt_device *dev)
+{
+	char dirname[PATH_MAX];
+	DIR *dir;
+	struct dirent *e;
+
+	snprintf(dirname, PATH_MAX, "%s/drm", dev->syspath);
+	dir = opendir(dirname);
+	if (!dir)
+		return;
+
+	while((e = readdir(dir))) {
+		int n;
+		if (sscanf(e->d_name, "card%d", &n) == 1)
+			dev->drm_card_path = g_strdup_printf("%s/%s",
+							     IGT_DRM_PATH,
+							     e->d_name);
+		else if (sscanf(e->d_name, "renderD%d", &n) == 1)
+			dev->drm_render_path = g_strdup_printf("%s/%s",
+							       IGT_DRM_PATH,
+							       e->d_name);
+	}
+	closedir(dir);
+
+	if (dev->drm_card_path)
+		DBG("card: %s\n", dev->drm_card_path);
+	if (dev->drm_render_path)
+		DBG("rend: %s\n", dev->drm_render_path);
+}
+
+static bool prepare_scan(void)
+{
+	if (!igt_devs.devs)
+		igt_devs.devs = g_ptr_array_sized_new(4);
+	if (!igt_devs.view)
+		igt_devs.view = g_ptr_array_sized_new(4);
+
+	if (!igt_devs.devs || !igt_devs.view)
+		return false;
+
+	return true;
+}
+
+static void set_udev_filter(struct udev_enumerate *enumerate,
+			    struct name_value *nv)
+{
+	/* Bypass filtering, we want to see all devices.
+	 * Likely used only in lsgpu.
+	*/
+	if (getenv("IGT_SCAN_GET_ALL_DEVICES"))
+		return;
+
+	while (nv->name) {
+		udev_enumerate_add_match_property(enumerate, nv->name,
+						  (const char *) nv->value);
+		nv++;
+	}
+}
+
+static void set_pci_udev_filter(struct udev_enumerate *enumerate)
+{
+	set_udev_filter(enumerate, &pci_udev_filters_attr_list[0]);
+}
+
+static void set_platform_udev_filter(struct udev_enumerate *enumerate)
+{
+	set_udev_filter(enumerate, &platform_udev_filters_attr_list[0]);
+}
+
+/* Subsystem scanning */
+struct {
+	unsigned int dev_type;
+
+	/* udev related */
+	const char *subsys;
+	void (*udev_filter)(struct udev_enumerate *enumerate);
+} cls[] = {
+	[IGT_SCAN_PCI] = { .dev_type = DEV_PCI,
+			   .subsys = "pci",
+			   .udev_filter = set_pci_udev_filter,
+			 },
+	[IGT_SCAN_PLATFORM] = { .dev_type = DEV_PLATFORM,
+				.subsys = "platform",
+				.udev_filter = set_platform_udev_filter,
+			      },
+};
+
+static void scan_devices(enum igt_devices_scan_type scantype)
+{
+	struct udev *udev;
+	struct udev_device *dev;
+	struct udev_enumerate *enumerate;
+	struct udev_list_entry *devices, *dev_list_entry;
+	int ret;
+
+	udev = udev_new();
+	igt_assert(udev);
+
+	enumerate = udev_enumerate_new(udev);
+	igt_assert(enumerate);
+
+	DBG("Scanning subsystem %s\n", cls[scantype].subsys);
+	ret = udev_enumerate_add_match_subsystem(enumerate, cls[scantype].subsys);
+	igt_assert(!ret);
+	cls[scantype].udev_filter(enumerate);
+
+	ret = udev_enumerate_scan_devices(enumerate);
+	igt_assert(!ret);
+
+	devices = udev_enumerate_get_list_entry(enumerate);
+	if (!devices)
+		return;
+
+	udev_list_entry_foreach(dev_list_entry, devices) {
+		struct igt_device *idev;
+		const char *path;
+
+		path = udev_list_entry_get_name(dev_list_entry);
+		dev = udev_device_new_from_syspath(udev, path);
+
+		idev = igt_device_new();
+		igt_assert(idev);
+
+		idev->dev_type = cls[scantype].dev_type;
+		idev->syspath = strdup(udev_device_get_syspath(dev));
+		get_props(dev, idev);
+		get_attrs(dev, idev);
+		find_drm_paths(idev);
+
+		switch (idev->dev_type) {
+		const char *tmp;
+		case DEV_PCI:
+			tmp = get_prop_pci_slot(idev);
+			set_vendor_device(idev);
+			idev->pretty_name = strdup(tmp ?: " ? ");
+			idev->vs = get_vendor_spec(idev->vendor);
+			break;
+		default:
+			tmp  = get_prop_modalias(idev);
+			idev->pretty_name = strdup(tmp ?: " ? ");
+		}
+
+		g_ptr_array_add(igt_devs.devs, idev);
+		g_ptr_array_add(igt_devs.view, idev);
+
+		udev_device_unref(dev);
+	}
+	udev_enumerate_unref(enumerate);
+	udev_unref(udev);
+}
+
+struct name_value filter_definition_list[];
+static void populate_gpu_data(void)
+{
+	fill_ht(&gpu_vendor_ht, &gpu_vendor_list[0]);
+	fill_ht(&filter_definition_ht, &filter_definition_list[0]);
+}
+
+static void igt_device_free(struct igt_device *dev)
+{
+	free(dev->pretty_name);
+	free(dev->device);
+	free(dev->drm_card_path);
+	free(dev->drm_render_path);
+	free(dev->drvpath);
+	free(dev->syspath);
+	free(dev->vendor);
+	g_hash_table_destroy(dev->attrs_ht);
+	g_hash_table_destroy(dev->props_ht);
+}
+
+void igt_devices_scan(bool force)
+{
+	if (force && igt_devs.devs_scanned) {
+		for (int i = 0; i < igt_devs.devs->len; i++) {
+			struct igt_device *dev = g_ptr_array_index(igt_devs.devs, i);
+			igt_device_free(dev);
+			free(dev);
+		}
+		igt_devs.devs_scanned = false;
+		g_ptr_array_free(igt_devs.view, true);
+		g_ptr_array_free(igt_devs.devs, true);
+		igt_devs.view = NULL;
+		igt_devs.devs = NULL;
+	}
+
+	if (igt_devs.devs_scanned)
+		return;
+
+	populate_blacklist_keys();      //keys from sysattr we skip
+	populate_gpu_data();
+
+	prepare_scan();
+	scan_devices(IGT_SCAN_PCI);
+	scan_devices(IGT_SCAN_PLATFORM);
+	igt_devs.devs_scanned = true;
+}
+
+#define pr_simple(k, v) printf("    %-16s: %s\n", k, v)
+#define pr_simple_prop(dev, key) pr_simple(key, get_prop(dev, key))
+#define pr_simple_attr(dev, key) pr_simple(key, get_attr(dev, key))
+
+static void igt_devs_print_simple(GPtrArray *view)
+{
+	struct igt_device *dev;
+	int i;
+
+	if (!view)
+		return;
+
+	for (i = 0; i < view->len; i++) {
+		dev = g_ptr_array_index(view, i);
+		printf("%s\n", dev->pretty_name);
+		pr_simple("syspath", dev->syspath);
+		pr_simple("drm card", dev->drm_card_path);
+		pr_simple("drm render", dev->drm_render_path);
+
+		if (get_prop_pci_id(dev))
+			pr_simple_prop(dev, "PCI_ID");
+		if (get_prop_driver(dev))
+			pr_simple_prop(dev, "DRIVER");
+	}
+}
+
+#define pr_detail(k, v) printf("%-32s: %s\n", k, v)
+
+static void print_ht(GHashTable *ht)
+{
+	GList *keys = g_hash_table_get_keys(ht);
+	keys = g_list_sort(keys, (GCompareFunc) strcmp);
+	while (keys) {
+		char *k = (char *) keys->data;
+		char *v = g_hash_table_lookup(ht, k);
+		pr_detail(k, v);
+		keys = g_list_next(keys);
+	}
+	g_list_free(keys);
+}
+
+static void igt_devs_print_detail(GPtrArray *view)
+{
+	struct igt_device *dev;
+	int i;
+
+	if (!view)
+		return;
+
+	for (i = 0; i < view->len; i++) {
+		dev = g_ptr_array_index(view, i);
+		printf("========== %s ==========\n", dev->pretty_name);
+		printf("[drm]\n");
+		pr_detail("card device", dev->drm_card_path);
+		pr_detail("render device", dev->drm_render_path);
+		pr_detail("syspath", dev->syspath);
+
+		printf("\n[properties]\n");
+		print_ht(dev->props_ht);
+		printf("\n[attributes]\n");
+		print_ht(dev->attrs_ht);
+	}
+}
+
+static struct print_func {
+	void (*prn)(GPtrArray *);
+} print_functions[] = {
+	[IGT_PRINT_SIMPLE] = { .prn = igt_devs_print_simple },
+	[IGT_PRINT_DETAIL] = { .prn = igt_devs_print_detail },
+};
+
+void igt_devices_print(enum igt_devices_print_type printtype)
+{
+	print_functions[printtype].prn(igt_devs.view);
+}
+
+void igt_devices_print_vendors(void)
+{
+	struct name_value *list = &gpu_vendor_list[0];
+	printf("Recognized vendors:\n");
+
+	printf("%-8s %-16s\n", "PCI ID", "vendor");
+	while (list->name) {
+		struct vendor_spec *vs = (struct vendor_spec *) list->value;
+		if (vs)
+			printf("%-8s %-16s\n", list->name, vs->vendor);
+		list++;
+	}
+}
+
+/* ------------------------------------------------------------------------- */
+#define FILTER_SPEC_NAME_LEN 32
+#define FILTER_SPEC_DATA_LEN 256
+struct filter_spec {
+	char name[FILTER_SPEC_NAME_LEN];
+	char data[FILTER_SPEC_DATA_LEN];
+};
+
+struct filter_func {
+	GPtrArray *(*filter_function)(struct filter_spec *fspec,
+				      struct filter_func *ffunc);
+	const char *help;
+	const char *detail;
+
+	const char *property;
+	const char *value;
+};
+
+struct filter_data {
+	char *vendor;
+	char *device;
+	char *card;
+};
+
+static GHashTable *split_filter_data(const char *data)
+{
+	GHashTable *ht = g_hash_table_new_full(g_str_hash, g_str_equal,
+					       free, free);
+	gchar **s;
+	gchar **strv = g_strsplit(data, ",", -1);
+
+	s = strv;
+	while (*s) {
+		char *k, *v;
+		v = strchr(*s, '=');
+		if (!v) {
+			s++;
+			continue;
+		}
+		k = strndup(*s, v - (*s));
+		v = strdup(v + 1);
+		g_hash_table_insert(ht, k, v);
+		s++;
+	}
+	g_strfreev(strv);
+
+	return ht;
+}
+
+static bool get_filter_spec(const char *filter, struct filter_spec *spec)
+{
+	if (!filter)
+		return false;
+
+	if (sscanf(filter, "%31[^:]:%255s", spec->name, spec->data) >= 1)
+		return true;
+
+	return false;
+}
+
+static GPtrArray *filter_drm(struct filter_spec *fspec,
+			     struct filter_func *ffunc)
+{
+	GHashTable *ht;
+	GPtrArray *devs, *view;
+	struct filter_data fdata;
+	int card = -1;
+	char cardstr[NAME_MAX];
+
+	(void) ffunc;
+	DBG("filter drm\n");
+
+	ht = split_filter_data(fspec->data);
+	fdata.card = g_hash_table_lookup(ht, "card");
+
+	view = igt_devs.view;
+	devs = igt_devs.devs;
+
+	if (fdata.card) {
+		sscanf(fdata.card, "%d", &card);
+		if (card < 0) {
+			g_hash_table_destroy(ht);
+			return view;
+		}
+	} else {
+		card = 0;
+	}
+
+	snprintf(cardstr, NAME_MAX, "%s/card%d", IGT_DRM_PATH, card);
+	for (int i = 0; i < devs->len; i++) {
+		struct igt_device *dev = g_ptr_array_index(devs, i);
+		if (dev->drm_card_path && !strcmp(cardstr, dev->drm_card_path)) {
+			g_ptr_array_add(view, dev);
+			break;
+		}
+	}
+
+	DBG("Filter drm view size: %d\n", view->len);
+
+	g_hash_table_destroy(ht);
+
+	return view;
+}
+
+static GPtrArray *filter_pci(struct filter_spec *fspec,
+			     struct filter_func *ffunc)
+{
+	GHashTable *ht;
+	GPtrArray *devs, *view;
+	struct filter_data fdata;
+	int card = -1;
+
+	DBG("filter pci\n");
+
+	(void) ffunc;
+	ht = split_filter_data(fspec->data);
+	fdata.vendor = g_hash_table_lookup(ht, "vendor");
+	fdata.device = g_hash_table_lookup(ht, "device");
+	fdata.card = g_hash_table_lookup(ht, "card");
+
+	view = igt_devs.view;
+	devs = igt_devs.devs;
+
+	if (fdata.card) {
+		sscanf(fdata.card, "%d", &card);
+		if (card < 0) {
+			g_hash_table_destroy(ht);
+			return view;
+		}
+	} else {
+		card = 0;
+	}
+
+	for (int i = 0; i < devs->len; i++) {
+		struct igt_device *dev = g_ptr_array_index(devs, i);
+
+		if (dev->dev_type != DEV_PCI)
+			continue;
+
+		/* Skip if 'vendor' doesn't match (hex or name) */
+		if (fdata.vendor &&
+				strcasecmp(fdata.vendor, dev->vendor) &&
+				!is_pci_vendor_name(fdata.vendor))
+			continue;
+
+		/* Skip if 'device' doesn't match */
+		if (fdata.device &&
+				strcasecmp(fdata.device, dev->device))
+			continue;
+
+		if (!card) {
+			g_ptr_array_add(view, dev);
+			break;
+		}
+		card--;
+	}
+
+	DBG("Filter pci view size: %d\n", view->len);
+
+	g_hash_table_destroy(ht);
+
+	return view;
+}
+
+/* Helper for finding first device matching property:value. */
+static GPtrArray *filter_property(struct filter_spec *fspec,
+				  struct filter_func *ffunc)
+{
+	GHashTable *ht;
+	GPtrArray *devs, *view;
+	struct filter_data fdata;
+	int card = -1;
+	const char *property = ffunc->property;
+	const char *value = ffunc->value;
+
+	if (!property || !value)
+		return igt_devs.view;
+
+	DBG("filter property/value [%s/%s]\n", property, value);
+
+	ht = split_filter_data(fspec->data);
+	fdata.card = g_hash_table_lookup(ht, "card");
+
+	view = igt_devs.view;
+	devs = igt_devs.devs;
+
+	if (fdata.card) {
+		sscanf(fdata.card, "%d", &card);
+		if (card < 0) {
+			g_hash_table_destroy(ht);
+			return view;
+		}
+	} else {
+		card = 0;
+	}
+
+	for (int i = 0; i < devs->len; i++) {
+		const char *prop_value;
+		struct igt_device *dev = g_ptr_array_index(devs, i);
+
+		prop_value = get_prop(dev, property);
+		if (prop_value && !strcmp(prop_value, value)) {
+			if (!card) {
+				g_ptr_array_add(view, dev);
+				break;
+			}
+			card--;
+		}
+	}
+
+	DBG("Filter view size: %d\n", view->len);
+
+	g_hash_table_destroy(ht);
+
+	return view;
+}
+
+static GPtrArray *filter_vgem(struct filter_spec *fspec,
+			      struct filter_func *ffunc)
+{
+	GPtrArray *view = filter_property(fspec, ffunc);
+	if (view) {
+		struct igt_device *dev = g_ptr_array_index(view, 0);
+		dev->vs = &v_vgem;
+	}
+	return view;
+}
+
+static GPtrArray *filter_vkms(struct filter_spec *fspec,
+			      struct filter_func *ffunc)
+{
+	return filter_property(fspec, ffunc);
+}
+
+static GPtrArray *filter_vc4(struct filter_spec *fspec,
+			     struct filter_func *ffunc)
+{
+	GPtrArray *view = filter_property(fspec, ffunc);
+	if (view) {
+		struct igt_device *dev = g_ptr_array_index(view, 0);
+		dev->vs = &v_vc4;
+	}
+	return filter_property(fspec, ffunc);
+}
+
+static struct filter_func f_drm = { .filter_function = filter_drm,
+				    .help = "drm:[card=%d]",
+				    .detail = "card is N-card number (from /dev/dri/cardN)\n",
+				  };
+
+static struct filter_func f_pci = { .filter_function = filter_pci,
+				    .help = "pci:[vendor=%04x/name][,device=%04x][,card=%d]\n",
+				    .detail = "vendor is hex number or name",
+				  };
+
+static struct filter_func f_vgem = { .filter_function = filter_vgem,
+				     .help = "vgem:[card=%d]",
+				     .detail = "card is n-th vgem card number\n",
+				     .property = "MODALIAS",
+				     .value = "platform:vgem",
+				   };
+
+static struct filter_func f_vkms = { .filter_function = filter_vkms,
+				     .help = "vkms:[card=%d]",
+				     .detail = "card is n-th vkms card number\n",
+				     .property = "MODALIAS",
+				     .value = "platform:vkms",
+				   };
+
+static struct filter_func f_vc4 = { .filter_function = filter_vc4,
+				    .help = "vc4:[card=%d]",
+				    .detail = "card is n-th vc4 card number\n",
+				    .property = "OF_COMPATIBLE_0",
+				    .value = "brcm,bcm2835-vc4",
+				  };
+
+struct name_value filter_definition_list[] = {
+	{ "drm",     (gpointer) &f_drm },
+	{ "pci",     (gpointer) &f_pci },
+	{ "vgem",    (gpointer) &f_vgem },
+	{ "vkms",    (gpointer) &f_vkms },
+	{ "vc4",     (gpointer) &f_vc4 },
+	{ NULL, },
+};
+
+void igt_device_print_filter_types(void)
+{
+	struct name_value *list = &filter_definition_list[0];
+
+	printf("Filter types:\n---\n");
+	printf("%-8s  %s\n---\n", "filter", "syntax");
+	while (list->name) {
+		struct filter_func *v = (struct filter_func *) list->value;
+		printf("%-8s  %s\n", list->name, v->help);
+		printf("%-8s  %s\n", "", v->detail);
+		list++;
+	}
+}
+
+static GPtrArray *device_filters = NULL;
+
+#define DEVICE_FILTER_CHECK_ALLOC() \
+	do { \
+		if (!device_filters) \
+			device_filters = g_ptr_array_new_full(2, free); \
+		igt_assert(device_filters); \
+	} while(0)
+
+int igt_device_filter_count(void)
+{
+	DEVICE_FILTER_CHECK_ALLOC();
+
+	return device_filters->len;
+}
+
+int igt_device_filter_add(const char *filter)
+{
+	gchar **strv, **s;
+	int c = 0;
+
+	DEVICE_FILTER_CHECK_ALLOC();
+
+	strv = g_strsplit(filter, ";", -1);
+
+	s = strv;
+	while (*s) {
+		g_ptr_array_add(device_filters, strdup(*s));
+		s++;
+	}
+	g_strfreev(strv);
+
+	return c;
+}
+
+const char *igt_device_filter_get(int num)
+{
+	DEVICE_FILTER_CHECK_ALLOC();
+
+	if (num < 0 || num >= device_filters->len)
+		return NULL;
+
+	return g_ptr_array_index(device_filters, num);
+}
+
+#define dev_is_non_discoverable(vs) \
+	((vs)->dev_type == DEV_PLATFORM_NON_DISCOVERABLE)
+
+bool igt_device_filter_apply(const char *filter)
+{
+	struct filter_spec fspec;
+	struct filter_func *ffunc;
+	bool ret;
+
+	if (!filter)
+		return false;
+
+	ret = get_filter_spec(filter, &fspec);
+	if (!ret) {
+		igt_warn("Can't split filter [%s]\n", filter);
+		return false;
+	}
+
+	ffunc = g_hash_table_lookup(filter_definition_ht, fspec.name);
+	if (!ffunc) {
+		igt_warn("No filter with name [%s]\n", fspec.name);
+		return false;
+	}
+
+	/* Clean view */
+	g_ptr_array_remove_range(igt_devs.view, 0, igt_devs.view->len);
+
+	ffunc->filter_function(&fspec, ffunc);
+
+	return true;
+}
+
+bool igt_device_filter_apply_nth(int num)
+{
+	const char *filter = igt_device_filter_get(num);
+	if (!filter)
+		return false;
+
+	return igt_device_filter_apply(filter);
+}
+
+#define safe_strncpy(dst, src, size) \
+	if (src) strncpy((dst), (src), (size))
+/*
+ * Returns:
+ * false - no card pointer was passed or card wasn't matched,
+ * true - card matched and returned.
+ */
+bool igt_device_card_match(const char *filter, struct igt_device_card *card)
+{	
+	struct igt_device *dev = NULL;
+
+	if (!card)
+		return false;
+	memset(card, 0, sizeof(*card));
+
+	igt_devices_scan(false);
+
+	if (igt_device_filter_apply(filter) == false)
+		return false;
+
+	if (!igt_devs.view->len)
+		return false;
+
+	/* We take first one if more than one card matches filter */
+	dev = g_ptr_array_index(igt_devs.view, 0);
+	card->dev_type = dev->dev_type;
+	card->chipset = DRIVER_ANY;
+	if (dev->vs)
+		card->chipset = dev->vs->chipset;
+	if (dev->dev_type == DEV_PCI) {
+		safe_strncpy(card->pci_slot, get_prop_pci_slot(dev),
+			PCI_SLOT_LENGTH);
+	}
+	safe_strncpy(card->card, dev->drm_card_path, NAME_MAX);
+	safe_strncpy(card->render, dev->drm_render_path, NAME_MAX);
+
+	return true;
+}
diff --git a/lib/igt_device_scan.h b/lib/igt_device_scan.h
new file mode 100644
index 00000000..b03b3007
--- /dev/null
+++ b/lib/igt_device_scan.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __IGT_DEVICE_SCAN_H__
+#define __IGT_DEVICE_SCAN_H__
+
+#include <limits.h>
+#include <igt.h>
+
+enum igt_devices_print_type {
+	IGT_PRINT_SIMPLE,
+	IGT_PRINT_DETAIL,
+};
+
+enum igt_device_type {
+	DEV_PCI,
+	DEV_PLATFORM,
+};
+
+#define PCI_SLOT_LENGTH 13
+struct igt_device_card {
+	enum igt_device_type dev_type;
+	int chipset;
+	char card[NAME_MAX];
+	char render[NAME_MAX];
+	char pci_slot[PCI_SLOT_LENGTH];
+};
+
+/* Scan udev subsystems. Do it once unless force is true, what rescans
+ * devices again */
+void igt_devices_scan(bool force);
+
+void igt_devices_print(enum igt_devices_print_type printtype);
+void igt_devices_print_vendors(void);
+void igt_device_print_filter_types(void);
+
+/*
+ * Handle device filter collection array.
+ * IGT can store/retrieve filters passed by user using '--device' args.
+*/
+
+/* Return number of filters collected */
+int igt_device_filter_count(void);
+
+/* Add filter(s) to the array. Returns number of filters added (>1 if
+ * user passes more than one filter separatined by ';') */
+int igt_device_filter_add(const char *filter);
+
+/* Get n-th filter stored in the array or NULL */
+const char *igt_device_filter_get(int num);
+
+/* Create devices view according to filter passed as an argument */
+bool igt_device_filter_apply(const char *filter);
+
+/* Apply n-th filter and create the view */
+bool igt_device_filter_apply_nth(int num);
+
+/* Use filter to match the device and fill card structure */
+bool igt_device_card_match(const char *filter, struct igt_device_card *card);
+
+#endif /* __IGT_DEVICE_SCAN_H__ */
diff --git a/lib/meson.build b/lib/meson.build
index 157624e7..826ebbe3 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -10,6 +10,7 @@ lib_sources = [
 	'igt_color_encoding.c',
 	'igt_debugfs.c',
 	'igt_device.c',
+	'igt_device_scan.c',
 	'igt_aux.c',
 	'igt_gpu_power.c',
 	'igt_gt.c',
diff --git a/tools/Makefile.sources b/tools/Makefile.sources
index 50706f41..0e67b654 100644
--- a/tools/Makefile.sources
+++ b/tools/Makefile.sources
@@ -33,6 +33,7 @@ tools_prog_lists =		\
 	intel_watermark		\
 	intel_gem_info		\
 	intel_gvtg_test     \
+	lsgpu			\
 	$(NULL)
 
 dist_bin_SCRIPTS = intel_gpu_abrt
diff --git a/tools/lsgpu.c b/tools/lsgpu.c
new file mode 100644
index 00000000..4c221ad5
--- /dev/null
+++ b/tools/lsgpu.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "igt_device_scan.h"
+#include "igt.h"
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+
+enum {
+	OPT_PRINT_SIMPLE   = 'i',
+	OPT_PRINT_DETAIL   = 'd',
+	OPT_DISPLAY_ALL    = 'a',
+	OPT_LIST_VENDORS   = 'v',
+	OPT_LIST_FILTERS   = 'l',
+	OPT_SET_FILTER     = 's',
+	OPT_DEVICE         = 'D',
+	OPT_HELP           = 'h'
+};
+
+bool g_display_all;
+bool g_show_vendors;
+char *g_pci_id;
+bool g_list_filters;
+char *g_filter;
+bool g_device;
+bool g_help;
+
+static const char *usage_str =
+	"usage: lsgpu [options]\n\n"
+	"Options:\n"
+	"  -i, --print-simple          Print devices as simple list (default)\n"
+	"  -d, --print-details         Print devices with details\n"
+	"  -a, --display-all           Don't apply udev filters and print all devices\n"
+	"  -v, --list-vendors          List recognized vendors\n"
+	"  -l, --list-filter-types     List registered device filters types\n"
+	"  -s, --set-filter filter     Set filter for processing devices\n"
+	"  -D, --device filter         Device filter, can be given multiple times\n"
+	"  -h, --help                  Show this help message and exit\n";
+
+static void test_device_open(struct igt_device_card *card)
+{
+	int fd;
+
+	if (!card)
+		return;
+
+	fd = __drm_open_card(card);
+	if (fd >= 0) {
+		printf("Device %s successfully opened\n", card->card);
+		close(fd);
+	} else {
+		if (strlen(card->card))
+			printf("Cannot open card %s device\n", card->card);
+		else
+			printf("Cannot open card device, empty name\n");
+	}
+
+	fd = __drm_open_render(card);
+	if (fd >= 0) {
+		printf("Device %s successfully opened\n", card->render);
+		close(fd);
+	} else {
+		if (strlen(card->render))
+			printf("Cannot open render %s device\n", card->render);
+		else
+			printf("Cannot open render device, empty name\n");
+	}
+}
+
+static void print_card(struct igt_device_card *card)
+{
+	if (!card)
+		return;
+
+	switch (card->dev_type) {
+	case DEV_PCI:
+		printf("dev type    : %s\n", "PCI");
+		printf("PCI slot    : %s\n", card->pci_slot);
+		break;
+	case DEV_PLATFORM:
+		printf("dev type    : %s\n", "PLATFORM");
+		break;
+	}
+	printf("chipset     : %x\n", card->chipset);
+	printf("drm card    : %s\n", card->card);
+	printf("drm render  : %s\n", card->render);
+}
+
+int main(int argc, char *argv[])
+{
+	static struct option long_options[] = {
+		{"print-simple",      no_argument,       NULL, OPT_PRINT_SIMPLE},
+		{"print-detail",      no_argument,       NULL, OPT_PRINT_DETAIL},
+		{"display-all",       no_argument,       NULL, OPT_DISPLAY_ALL},
+		{"list-vendors",      no_argument,       NULL, OPT_LIST_VENDORS},
+		{"list-filter-types", no_argument,       NULL, OPT_LIST_FILTERS},
+		{"set-filter",        required_argument, NULL, OPT_SET_FILTER},
+		{"device",            required_argument, NULL, OPT_DEVICE},
+		{"help",              no_argument,       NULL, OPT_HELP},
+		{0, 0, 0, 0}
+	};
+	int c, index = 0;
+	const char *env;
+	enum igt_devices_print_type printtype = IGT_PRINT_SIMPLE;
+
+	while ((c = getopt_long(argc, argv, "idavls:D:h",
+				long_options, &index)) != -1) {
+		switch(c) {
+		case OPT_PRINT_SIMPLE:
+			printtype = IGT_PRINT_SIMPLE;
+			break;
+		case OPT_PRINT_DETAIL:
+			printtype = IGT_PRINT_DETAIL;
+			break;
+		case OPT_DISPLAY_ALL:
+			g_display_all = true;
+			break;
+		case OPT_LIST_VENDORS:
+			g_show_vendors = true;
+			break;
+		case OPT_SET_FILTER:
+			g_filter = strdup(optarg);
+			break;
+		case OPT_LIST_FILTERS:
+			g_list_filters = true;
+			break;
+		case OPT_DEVICE:
+			g_device = true;
+			igt_device_filter_add(optarg);
+			break;
+		case OPT_HELP:
+			g_help = true;
+			break;
+		}
+	}
+
+	if (g_help) {
+		printf("%s\n", usage_str);
+		exit(0);
+	}
+
+	env = getenv("IGT_DEVICE");
+	if (env) {
+		igt_device_filter_add(env);
+		g_device = true;
+	}
+
+	if (g_show_vendors) {
+		igt_devices_print_vendors();
+		return 0;
+	}
+
+	if (g_list_filters) {
+		igt_device_print_filter_types();
+		return 0;
+	}
+
+	/* This variable is used only for lsgpu to display information
+	 * about all devices in udev */
+	if (g_display_all)
+		setenv("IGT_SCAN_GET_ALL_DEVICES", "1", 0);
+	igt_devices_scan(false);
+
+	if (g_filter) {
+		igt_device_filter_apply(g_filter);
+	}
+
+	if (g_device) {
+		int n = igt_device_filter_count();
+		printf("=== Device filter list ===\n");
+		for (int i = 0; i < n; i++) {
+			printf("[%2d]: %s\n", i,
+			       igt_device_filter_get(i));
+			igt_device_filter_apply_nth(i);
+			igt_devices_print(printtype);
+		}
+		printf("\n");
+
+		printf("=== Testing device open ===\n");
+		for (int i = 0; i < n; i++) {
+			struct igt_device_card card;
+			const char *filter = igt_device_filter_get(i);
+
+			if (!igt_device_card_match(filter, &card))
+				continue;
+			print_card(&card);
+			test_device_open(&card);
+			printf("---\n");
+		}
+
+		return 0;
+	}
+
+	igt_devices_print(printtype);
+
+	return 0;
+}
diff --git a/tools/meson.build b/tools/meson.build
index 6e72b263..9b3a2a69 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -36,6 +36,7 @@ tools_progs = [
 	'intel_gem_info',
 	'intel_gvtg_test',
 	'dpcd_reg',
+	'lsgpu',
 ]
 tool_deps = igt_deps
 
-- 
2.21.0



More information about the igt-dev mailing list