[igt-dev] [PATCH i-g-t v3 1/2] Introduce multi-device selection API
Zbigniew Kempczyński
zbigniew.kempczynski at intel.com
Fri Aug 9 10:19:21 UTC 2019
Change adds device selection based on scanning drm subsystem
using udev library.
Tool 'lsgpu' which uses device scanning and selection feature was added.
---
.../igt-gpu-tools/igt-gpu-tools-docs.xml | 1 +
lib/Makefile.sources | 2 +
lib/igt_device_scan.c | 1188 +++++++++++++++++
lib/igt_device_scan.h | 69 +
lib/meson.build | 1 +
tools/Makefile.sources | 1 +
tools/lsgpu.c | 183 +++
tools/meson.build | 1 +
8 files changed, 1446 insertions(+)
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/reference/igt-gpu-tools/igt-gpu-tools-docs.xml b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
index ac83272f..4b3c38af 100644
--- a/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
+++ b/docs/reference/igt-gpu-tools/igt-gpu-tools-docs.xml
@@ -23,6 +23,7 @@
<xi:include href="xml/igt_core.xml"/>
<xi:include href="xml/igt_debugfs.xml"/>
<xi:include href="xml/igt_device.xml"/>
+ <xi:include href="xml/igt_device_scan.xml"/>
<xi:include href="xml/igt_draw.xml"/>
<xi:include href="xml/igt_dummyload.xml"/>
<xi:include href="xml/igt_fb.xml"/>
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/igt_device_scan.c b/lib/igt_device_scan.c
new file mode 100644
index 00000000..2446c31a
--- /dev/null
+++ b/lib/igt_device_scan.c
@@ -0,0 +1,1188 @@
+/*
+ * 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: Multi-device scanning and selection
+ * @title: Multi-device selection
+ * @include: igt.h
+ *
+ * Device selection scans devices on 'drm' subsystem using udev library.
+ * For each drm device we also get and store its parent in scanned device array
+ * to allow device selection in more contextual way.
+ *
+ * Parent devices are bus devices (like pci, platform, etc.) and contain a lot
+ * of usable information than single drm device for implementing filters.
+ *
+ * Udev contains properties and sysattrs for device as a list what is not
+ * convinient for key/value searching. So udev device is rewritten
+ * to internal igt_device to handle properties and sysattrs in hash tables
+ * to get an instant access to the key/value.
+ *
+ * Device selection can be done using filters. Direct device selection filter
+ * is special filter and it is checked first. Then contextual filter is chosen
+ * depending on filter name.
+ *
+ * Direct device selection filter must be:
+ *
+ * subsystem:/sys
+ *
+ * So, when user passes filter which looks like follows:
+ * - drm:/sys/devices/pci0000:00/0000:00:02.0/drm/card0
+ * - pci:/sys/devices/pci0000:00/0000:00:02.0
+ * - platform:/sys/devices/platform/vgem
+ *
+ * device from array which have subsystem and sys path will be returned.
+ *
+ * When /sys is not specified after colon contextual filters are taken
+ * into account. Drm device occurs in the system when appropriate module
+ * is loaded and device is detected (or exposed for platform devices). Loading
+ * drivers in different order can be problematic from CI point of view, where
+ * you want to get same device especially when multiple gpu devices can reside
+ * in the system. For such devices its parent location on pci bus is constant
+ * and allows appropriate device selection using for example vendor / device
+ * ids.
+ *
+ * For tests which opens more than one device device filter collection API
+ * can be used. You can add filter to the array using igt_device_filter_add(),
+ * get nth filter stored using igt_device_filter_get() and return
+ * igt_device_card using filter in igt_device_card_match().
+ *
+*/
+
+//#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 strequal(x, y) ((x) && (y) && !strcmp((x), (y)))
+#define IGT_DRM_PATH "/dev/dri"
+
+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 {
+ const char *vendor;
+ const char *match;
+ int chipset;
+};
+
+struct igt_device {
+ /* Filled for drm devices */
+ struct igt_device *parent;
+
+ /* Point to vendor spec if can be found */
+ struct vendor_spec *vs;
+
+ /* Properties / sysattrs rewriten from udev lists */
+ GHashTable *props_ht;
+ GHashTable *attrs_ht;
+
+ /* Most usable variables from udev device */
+ char *subsystem;
+ char *syspath;
+ char *devnode;
+
+ /* /dev/dri/... paths */
+ char *drm_card;
+ char *drm_render;
+
+ /* For pci subsystem */
+ char *vendor;
+ char *device;
+};
+
+/* Scanned devices */
+struct igt_devices {
+ GPtrArray *devs; //all devices
+ GPtrArray *view; //filtered view
+ bool devs_scanned;
+};
+
+/* Scanned devices holder */
+static struct igt_devices igt_devs;
+
+static struct vendor_spec v_intel = { .vendor = "Intel",
+ .chipset = DRIVER_INTEL
+ };
+static struct vendor_spec v_amd = { .vendor = "AMD",
+ .chipset = DRIVER_AMDGPU
+ };
+static struct vendor_spec v_vgem = { .vendor = "Virtual-GEM",
+ .chipset = DRIVER_VGEM
+ };
+static struct vendor_spec v_vc4 = { .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 list and compare against vendor field.
+ * Used mostly with vendor=... filter parameter when PCI id is not matched.
+*/
+static const char *get_pci_vendor_id_by_name(const char *name)
+{
+ struct name_value *data = &gpu_vendor_list[0];
+
+ while (data->name) {
+ struct vendor_spec *vs = (struct vendor_spec *) data->value;
+ if (!strcasecmp(name, vs->vendor))
+ return data->name;
+ data++;
+ }
+
+ return NULL;
+}
+
+/* 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", "modes",
+ "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. Resolves 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_subsystem(dev) get_prop(dev, "SUBSYSTEM")
+#define is_drm_subsystem(dev) (!strcmp(get_prop_subsystem(dev), "drm"))
+#define is_pci_subsystem(dev) (!strcmp(get_prop_subsystem(dev), "pci"))
+#define is_platform_subsystem(dev) (!strcmp(get_prop_subsystem(dev), "platform"))
+
+/* 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(dev, "PCI_ID");
+ if (!pci_id || strlen(pci_id) != 9)
+ return;
+ dev->vendor = strndup(pci_id, 4);
+ dev->device = strndup(pci_id + 5, 4);
+}
+
+/* Allocate arrays for keeping scanned devices */
+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;
+}
+
+/* Create new igt_device from udev device.
+ Fills structure with most usable udev device variables, properties
+ and sysattrs.
+*/
+static struct igt_device *igt_device_new_from_udev(struct udev_device *dev)
+{
+ struct igt_device *idev = igt_device_new();
+ igt_assert(idev);
+
+ idev->syspath = g_strdup(udev_device_get_syspath(dev));
+ idev->subsystem = g_strdup(udev_device_get_subsystem(dev));
+ idev->devnode = g_strdup(udev_device_get_devnode(dev));
+
+ if (idev->devnode && strstr(idev->devnode, "/dev/dri/card"))
+ idev->drm_card = g_strdup(idev->devnode);
+ else if (idev->devnode && strstr(idev->devnode, "/dev/dri/render"))
+ idev->drm_render = g_strdup(idev->devnode);
+
+ get_props(dev, idev);
+ get_attrs(dev, idev);
+
+ return idev;
+}
+
+/* Iterate over all igt_devices array and find one matched to
+ * subsystem and syspath.
+*/
+static struct igt_device *igt_device_find(const char *subsystem,
+ const char *syspath)
+{
+ struct igt_device *dev;
+
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ dev = g_ptr_array_index(igt_devs.devs, i);
+ if (!strcmp(dev->subsystem, subsystem) &&
+ !strcmp(dev->syspath, syspath))
+ return dev;
+ }
+ return NULL;
+}
+
+/* For each drm igt_device add or update its parent igt_device to the array.
+ As card/render drm devices mostly have same parent (vkms is an exception)
+ link to it and update corresponding drm_card / drm_render fields.
+*/
+static void update_or_add_parent(struct udev_device *dev,
+ struct igt_device *idev)
+{
+ struct udev_device *parent_dev;
+ struct igt_device *parent_idev;
+ const char *subsystem, *syspath, *devname;
+
+ parent_dev = udev_device_get_parent(dev);
+ igt_assert(parent_dev);
+
+ subsystem = udev_device_get_subsystem(parent_dev);
+ syspath = udev_device_get_syspath(parent_dev);
+
+ parent_idev = igt_device_find(subsystem, syspath);
+ if (!parent_idev) {
+ parent_idev = igt_device_new_from_udev(parent_dev);
+ if (is_pci_subsystem(parent_idev)) {
+ set_vendor_device(parent_idev);
+ parent_idev->vs = get_vendor_spec(parent_idev->vendor);
+ }
+ g_ptr_array_add(igt_devs.devs, parent_idev);
+ }
+ devname = udev_device_get_devnode(dev);
+ if (strstr(devname, "/dev/dri/card"))
+ parent_idev->drm_card = g_strdup(devname);
+ else if (strstr(devname, "/dev/dri/render"))
+ parent_idev->drm_render= g_strdup(devname);
+
+ idev->parent = parent_idev;
+}
+
+static gint devs_compare(gconstpointer a, gconstpointer b)
+{
+ struct igt_device *dev1, *dev2;
+ int ret;
+
+ dev1 = *(struct igt_device **) a;
+ dev2 = *(struct igt_device **) b;
+ ret = strcmp(dev1->subsystem, dev2->subsystem);
+ if (ret)
+ return ret;
+
+ return strcmp(dev1->syspath, dev2->syspath);
+}
+
+/* Core scanning function.
+ *
+ * All scanned devices are kept inside igt_devs.devs pointer array.
+ * Each added device is igt_device structure, which contrary to udev device
+ * has properties / sysattrs stored inside hash table instead of list.
+ *
+ * Function iterates over devices on 'drm' subsystem. For each drm device
+ * its parent is taken (bus device) and stored inside same array.
+ * Function sorts all found devices to keep same order of bus devices
+ * for providing predictable search.
+ */
+static void scan_drm_devices(void)
+{
+ struct udev *udev;
+ 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 drm subsystem\n");
+ ret = udev_enumerate_add_match_subsystem(enumerate, "drm");
+ igt_assert(!ret);
+
+ udev_enumerate_add_match_property(enumerate, "DEVNAME", "/dev/dri/*");
+ igt_assert(!ret);
+
+ 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) {
+ const char *path;
+ struct udev_device *dev;
+ struct igt_device *idev;
+
+ path = udev_list_entry_get_name(dev_list_entry);
+ dev = udev_device_new_from_syspath(udev, path);
+ idev = igt_device_new_from_udev(dev);
+ update_or_add_parent(dev, idev);
+ g_ptr_array_add(igt_devs.devs, idev);
+
+ udev_device_unref(dev);
+ }
+ udev_enumerate_unref(enumerate);
+ udev_unref(udev);
+
+ g_ptr_array_sort(igt_devs.devs, devs_compare);
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ g_ptr_array_add(igt_devs.view,
+ g_ptr_array_index(igt_devs.devs, i));
+ }
+}
+
+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->devnode);
+ free(dev->subsystem);
+ free(dev->syspath);
+ free(dev->drm_card);
+ free(dev->drm_render);
+ free(dev->vendor);
+ free(dev->device);
+ g_hash_table_destroy(dev->attrs_ht);
+ g_hash_table_destroy(dev->props_ht);
+}
+
+/**
+ * igt_devices_scan
+ * @force: enforce scanning devices
+ *
+ * Function scans udev in search of gpu devices. For first run it can be
+ * called with @force = false. If something changes during the the test
+ * or test does some module loading (new drm devices occurs during execution)
+ * function must be called again with @force = true to refresh device array.
+ */
+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_drm_devices();
+
+ igt_devs.devs_scanned = true;
+}
+
+#define pr_simple(k, v) printf(" %-16s: %s\n", k, v)
+#define pr_simple2(k, v1, v2) printf(" %-16s: %s:%s\n", k, v1, v2)
+#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:%s\n", dev->subsystem, dev->syspath);
+ if (dev->drm_card)
+ pr_simple("drm card", dev->drm_card);
+ if (dev->drm_render)
+ pr_simple("drm render", dev->drm_render);
+ if (is_drm_subsystem(dev)) {
+ pr_simple2("parent", dev->parent->subsystem,
+ dev->parent->syspath);
+ } else {
+ if (is_pci_subsystem(dev)) {
+ pr_simple("vendor", dev->vendor);
+ pr_simple("device", dev->device);
+ }
+ }
+ printf("\n");
+ }
+}
+
+#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:%s ==========\n",
+ dev->subsystem, dev->syspath);
+ if (!is_drm_subsystem(dev)) {
+ pr_detail("card device", dev->drm_card);
+ pr_detail("render device", dev->drm_render);
+ }
+
+ printf("\n[properties]\n");
+ print_ht(dev->props_ht);
+ printf("\n[attributes]\n");
+ print_ht(dev->attrs_ht);
+ printf("\n");
+ }
+}
+
+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 },
+};
+
+/**
+ * igt_devices_print
+ * @printtype: IGT_PRINT_SIMPLE or IGT_PRINT_DETAIL
+ *
+ * Function is used by 'lsgpu' tool to print device array in simple or detailed
+ * form. This function is added here instead in 'lsgpu' to avoid exposing
+ * internal implementation data structures.
+ */
+void igt_devices_print(enum igt_devices_print_type printtype)
+{
+ print_functions[printtype].prn(igt_devs.view);
+}
+
+/**
+ * igt_devices_print_vendors
+ *
+ * Print pci id -> vendor mappings. Vendor names printed by this function
+ * can be used for filters like pci which allows passing vendor - like
+ * vendor id (8086) or as a string (Intel).
+ */
+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;
+ char *drm;
+};
+
+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 bool is_vendor_matched(struct igt_device *dev, const char *vendor)
+{
+ const char *vendor_id;
+
+ if (!dev->vendor || !vendor)
+ return false;
+
+ /* First we compare vendor id, like 8086 */
+ if (!strcasecmp(dev->vendor, vendor))
+ return true;
+
+ /* Likely we have vendor string instead of id */
+ vendor_id = get_pci_vendor_id_by_name(vendor);
+ if (!vendor_id)
+ return false;
+
+ return !strcasecmp(dev->vendor, vendor_id);
+}
+
+/* Filter which matches subsystem:/sys/... path.
+ * Used as first filter in chain.
+ */
+static GPtrArray *filter_sys(struct filter_spec *fspec,
+ struct filter_func *ffunc)
+{
+ (void) ffunc;
+
+ DBG("filter sys\n");
+ if (!strlen(fspec->data))
+ return igt_devs.view;
+
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ struct igt_device *dev = g_ptr_array_index(igt_devs.devs, i);
+
+ if (strequal(dev->subsystem, fspec->name) &&
+ strequal(dev->syspath, fspec->data)) {
+ g_ptr_array_add(igt_devs.view, dev);
+ break;
+ }
+ }
+
+ DBG("Filter sys view size: %d\n", igt_devs.view->len);
+
+ return igt_devs.view;
+}
+
+/* Find drm device using direct path to /dev/dri/.
+ * It extends filter_sys to allow using drm:/dev/dri/cardX and
+ * drm:/dev/dri/renderDX filter syntax.
+ */
+static GPtrArray *filter_drm(struct filter_spec *fspec,
+ struct filter_func *ffunc)
+{
+ (void) ffunc;
+
+ DBG("filter drm\n");
+ if (!strlen(fspec->data))
+ return igt_devs.view;
+
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ struct igt_device *dev = g_ptr_array_index(igt_devs.devs, i);
+ if (!is_drm_subsystem(dev))
+ continue;
+
+ if (strequal(dev->syspath, fspec->data) ||
+ strequal(dev->drm_card, fspec->data) ||
+ strequal(dev->drm_render, fspec->data)) {
+ g_ptr_array_add(igt_devs.view, dev);
+ break;
+ }
+ }
+
+ DBG("Filter drm view size: %d\n", igt_devs.view->len);
+
+ return igt_devs.view;
+}
+
+/* Find appropriate pci device matching vendor/device/card filter arguments
+*/
+static GPtrArray *filter_pci(struct filter_spec *fspec,
+ struct filter_func *ffunc)
+{
+ GHashTable *ht;
+ struct filter_data fdata;
+ int card = -1;
+ (void) ffunc;
+
+ DBG("filter pci\n");
+
+ 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");
+
+ if (fdata.card) {
+ sscanf(fdata.card, "%d", &card);
+ if (card < 0) {
+ g_hash_table_destroy(ht);
+ return igt_devs.view;
+ }
+ } else {
+ card = 0;
+ }
+
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ struct igt_device *dev = g_ptr_array_index(igt_devs.devs, i);
+
+ if (!is_pci_subsystem(dev))
+ continue;
+
+ /* Skip if 'vendor' doesn't match (hex or name) */
+ if (fdata.vendor && !is_vendor_matched(dev, fdata.vendor))
+ continue;
+
+ /* Skip if 'device' doesn't match */
+ if (fdata.device && strcasecmp(fdata.device, dev->device))
+ continue;
+
+ /* We get n-th card */
+ if (!card) {
+ g_ptr_array_add(igt_devs.view, dev);
+ break;
+ }
+ card--;
+ }
+
+ DBG("Filter pci view size: %d\n", igt_devs.view->len);
+
+ g_hash_table_destroy(ht);
+
+ return igt_devs.view;
+}
+
+/* Helper for finding first device matching property:value.
+*/
+static GPtrArray *filter_property(struct filter_spec *fspec,
+ struct filter_func *ffunc)
+{
+ GHashTable *ht;
+ 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");
+
+ if (fdata.card) {
+ sscanf(fdata.card, "%d", &card);
+ if (card < 0) {
+ g_hash_table_destroy(ht);
+ return igt_devs.view;
+ }
+ } else {
+ card = 0;
+ }
+
+ for (int i = 0; i < igt_devs.devs->len; i++) {
+ const char *prop_value;
+ struct igt_device *dev = g_ptr_array_index(igt_devs.devs, i);
+
+ prop_value = get_prop(dev, property);
+ if (prop_value && !strcmp(prop_value, value)) {
+ if (!card) {
+ g_ptr_array_add(igt_devs.view, dev);
+ break;
+ }
+ card--;
+ }
+ }
+
+ DBG("Filter view size: %d\n", igt_devs.view->len);
+
+ g_hash_table_destroy(ht);
+
+ return igt_devs.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:/dev/dri/* path",
+ .detail = "find drm device by /dev/dri/* node\n",
+ };
+
+static struct filter_func f_pci = { .filter_function = filter_pci,
+ .help = "pci:[vendor=%04x/name][,device=%04x][,card=%d]",
+ .detail = "vendor is hex number or vendor name\n",
+ };
+
+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, },
+};
+
+/**
+ * @igt_device_print_filter_types
+ *
+ * Print all filters syntax for device selection.
+ */
+void igt_device_print_filter_types(void)
+{
+ struct name_value *list = &filter_definition_list[0];
+ printf("Filter types:\n---\n");
+ printf("%-12s %s\n---\n", "filter", "syntax");
+
+ printf("%-12s %s\n", "", "direct subsystem:/sys/... path selection\n");
+ printf("%-12s %s\n", "",
+ "ex: drm:/sys/devices/pci0000:00/0000:00:02.0/drm/card0");
+ printf("%-12s %s\n", "",
+ " pci:/sys/devices/pci0000:00/0000:00:02.0");
+ printf("%-12s %s\n", "",
+ " platform:/sys/devices/platform/vgem\n");
+
+ while (list->name) {
+ struct filter_func *v = (struct filter_func *) list->value;
+ printf("%-12s %s\n", list->name, v->help);
+ printf("%-12s %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)
+
+/**
+ * igt_device_filter_count
+ *
+ * Returns number of filters collected in the filter array.
+ */
+int igt_device_filter_count(void)
+{
+ DEVICE_FILTER_CHECK_ALLOC();
+
+ return device_filters->len;
+}
+
+/**
+ * igt_device_filter_add
+ * @filter: filter to be stored in filter array
+ *
+ * Function allows passing single or more filters within one string. This is
+ * for CI when it can extract filter from environment variable (and it must
+ * be single string). So if @filter contains semicolon ';' it treats
+ * each part as separate filter and adds to the filter array.
+ *
+ * Returns number of filters added to filter array. Can be greater than
+ * 1 if @filter contains more than one filter separated by semicolon.
+ */
+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;
+}
+
+/**
+ * igt_device_filter_get
+ * @num: Number of filter from filter array
+ *
+ * Returns filter string or NULL if @num is out of range of filter array.
+*/
+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);
+}
+
+static bool igt_device_filter_apply(const char *filter)
+{
+ struct filter_spec fspec;
+ bool ret;
+
+ if (!filter)
+ return false;
+
+ memset(&fspec, 0, sizeof(fspec));
+ ret = get_filter_spec(filter, &fspec);
+ if (!ret) {
+ igt_warn("Can't split filter [%s]\n", filter);
+ return false;
+ }
+
+ /* Clean view */
+ g_ptr_array_remove_range(igt_devs.view, 0, igt_devs.view->len);
+
+ /* If fspec.data contains "/sys" use direct path instead
+ * contextual filter */
+ if (!strncmp(fspec.data, "/sys", 4))
+ filter_sys(&fspec, NULL);
+ else {
+ struct filter_func *ffunc;
+ ffunc = g_hash_table_lookup(filter_definition_ht, fspec.name);
+ if (!ffunc) {
+ igt_warn("No filter with name [%s]\n", fspec.name);
+ return false;
+ }
+ ffunc->filter_function(&fspec, ffunc);
+ }
+
+ return true;
+}
+
+#define safe_strncpy(dst, src, size) \
+ if ((dst) && (src)) strncpy((dst), (src), (size))
+
+/**
+ * igt_device_card_match
+ * @filter: filter string
+ * @card: pointer to igt_device_card struct
+ *
+ * Function applies filter to match device from device array.
+ *
+ * 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->chipset = DRIVER_ANY;
+ if (dev->vs)
+ card->chipset = dev->vs->chipset;
+
+ safe_strncpy(card->subsystem, dev->subsystem, NAME_MAX);
+ safe_strncpy(card->card, dev->drm_card, NAME_MAX);
+ safe_strncpy(card->render, dev->drm_render, NAME_MAX);
+
+ return true;
+}
diff --git a/lib/igt_device_scan.h b/lib/igt_device_scan.h
new file mode 100644
index 00000000..62f7f39a
--- /dev/null
+++ b/lib/igt_device_scan.h
@@ -0,0 +1,69 @@
+/*
+ * 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,
+};
+
+struct igt_device_card {
+ int chipset;
+ char subsystem[NAME_MAX];
+ char card[NAME_MAX];
+ char render[NAME_MAX];
+};
+
+/* 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);
+
+/* 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..e723f0b2
--- /dev/null
+++ b/tools/lsgpu.c
@@ -0,0 +1,183 @@
+/*
+ * 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_DETAIL = 'D',
+ OPT_LIST_VENDORS = 'v',
+ OPT_LIST_FILTERS = 'l',
+ OPT_DEVICE = 'd',
+ OPT_HELP = 'h'
+};
+
+bool g_show_vendors;
+bool g_list_filters;
+bool g_device;
+bool g_help;
+
+static const char *usage_str =
+ "usage: lsgpu [options]\n\n"
+ "Options:\n"
+ " -D, --print-details Print devices with details\n"
+ " -v, --list-vendors List recognized vendors\n"
+ " -l, --list-filter-types List registered device filters types\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;
+
+ printf("subsystem : %s\n", card->subsystem);
+ 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-detail", no_argument, NULL, OPT_PRINT_DETAIL},
+ {"list-vendors", no_argument, NULL, OPT_LIST_VENDORS},
+ {"list-filter-types", no_argument, NULL, OPT_LIST_FILTERS},
+ {"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, "Dvld:h",
+ long_options, &index)) != -1) {
+ switch(c) {
+
+ case OPT_PRINT_DETAIL:
+ printtype = IGT_PRINT_DETAIL;
+ break;
+ case OPT_LIST_VENDORS:
+ g_show_vendors = true;
+ 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;
+ }
+
+ igt_devices_scan(false);
+
+ 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));
+ }
+ 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