[Spice-devel] [RFC linux vdagent] Add POC for getting xrandr output from monitor ID
Jonathon Jongsma
jjongsma at redhat.com
Mon Nov 5 23:09:33 UTC 2018
This is just a proof of concept utility that takes a PCI device address
and a monitor ID and finds the xrandr output associated with that
monitor id.
---
Changes in v2:
- used different format for specifying the PCI address
(pci/$domain/$dev.$fn)
- used err()/errx() to report errors (from err.h)
- Added some debug output (export DEBUG=1)
- read PCI address from sysfs by getting the link for
/sys/class/drm/card0 instead of /sys/class/drm/card0/device
- handle the full PCI heirarchy (including bridges)
- handle different vendor/device types by customizing the expected
xrandr names
- Query X outputs outside the loop so they don't get looked up for
every different device
- handle Nvidia devices that don't provide outputs via the DRM
subsystem by assuming that it's the only device being used by X and
simply looking up the Nth Xrandr output.
- added tests
- various other fixes
Makefile.am | 18 +
configure.ac | 1 +
src/vdagent/get-xrandr-output.c | 806 ++++++++++++++++++++++++++++++++
3 files changed, 825 insertions(+)
create mode 100644 src/vdagent/get-xrandr-output.c
diff --git a/Makefile.am b/Makefile.am
index 3e405bc..b159650 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,7 @@ NULL =
bin_PROGRAMS = src/spice-vdagent
sbin_PROGRAMS = src/spice-vdagentd
+noinst_PROGRAMS = src/get-xrandr-output
common_sources = \
src/udscs.c \
@@ -77,6 +78,23 @@ src_spice_vdagentd_SOURCES = \
src/vdagentd/virtio-port.h \
$(NULL)
+src_get_xrandr_output_SOURCES = \
+ src/vdagent/get-xrandr-output.c \
+ $(NULL)
+
+src_get_xrandr_output_CFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(DRM_CFLAGS) \
+ $(X_CFLAGS) \
+ $(GLIB2_CFLAGS) \
+ $(NULL)
+
+src_get_xrandr_output_LDADD = \
+ $(DRM_LIBS) \
+ $(X_LIBS) \
+ $(GLIB2_LIBS) \
+ $(NULL)
+
if HAVE_CONSOLE_KIT
src_spice_vdagentd_SOURCES += src/vdagentd/console-kit.c
else
diff --git a/configure.ac b/configure.ac
index 7cb44db..55b031e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,6 +105,7 @@ PKG_CHECK_MODULES(X, [xfixes xrandr >= 1.3 xinerama x11])
PKG_CHECK_MODULES(SPICE, [spice-protocol >= 0.12.13])
PKG_CHECK_MODULES(ALSA, [alsa >= 1.0.22])
PKG_CHECK_MODULES([DBUS], [dbus-1])
+PKG_CHECK_MODULES([DRM], [libdrm])
if test "$with_session_info" = "auto" || test "$with_session_info" = "systemd"; then
PKG_CHECK_MODULES([LIBSYSTEMD_LOGIN],
diff --git a/src/vdagent/get-xrandr-output.c b/src/vdagent/get-xrandr-output.c
new file mode 100644
index 0000000..c2116d2
--- /dev/null
+++ b/src/vdagent/get-xrandr-output.c
@@ -0,0 +1,806 @@
+/* get-xrandr-output.c proof of concept for converting PCI address and device
+ * display id to an xrandr output in the guest.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Jonathon Jongsma <jjongsma at redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <unistd.h>
+#include <X11/extensions/Xrandr.h>
+#include <glib.h>
+
+static bool debug = false;
+#define DEBUG(...) \
+ if (G_UNLIKELY(debug)) printf(__VA_ARGS__);
+
+typedef struct PciDevice {
+ int domain;
+ uint8_t bus;
+ uint8_t slot;
+ uint8_t function;
+} PciDevice;
+
+typedef struct PciAddress {
+ int domain;
+ GList *devices; /* PciDevice */
+} PciAddress;
+
+PciAddress* pci_address_new()
+{
+ return g_new0(PciAddress, 1);
+}
+
+void pci_address_free(PciAddress *addr)
+{
+ g_list_free_full(addr->devices, g_free);
+ g_free(addr);
+}
+
+
+int read_next_hex_number(const char *input, char delim, char **endptr)
+{
+ assert(input != NULL);
+ assert(endptr != NULL);
+
+ const char *pos = strchr(input, delim);
+ int n;
+ if (!pos) {
+ *endptr = NULL;
+ return 0;
+ }
+
+ char *endpos;
+ n = strtol(input, &endpos, 16);
+
+ // check if we read all characters until the delimiter
+ if (endpos != pos)
+ endpos = NULL;
+
+ *endptr = endpos;
+ return n;
+}
+
+// the device should be specified in BDF notation (e.g. 0000:00:02.0)
+// see https://wiki.xen.org/wiki/Bus:Device.Function_(BDF)_Notation
+bool parse_pci_device(const char *bdf, const char *end, PciDevice *device)
+{
+ if (!device)
+ return false;
+
+ char *pos;
+ device->domain = read_next_hex_number(bdf, ':', &pos);
+ if (!pos)
+ return false;
+
+ device->bus = read_next_hex_number(pos + 1, ':', &pos);
+ if (!pos)
+ return false;
+
+ device->slot = read_next_hex_number(pos + 1, '.', &pos);
+ if (!pos)
+ return false;
+
+ device->function = strtol(pos + 1, &pos, 16);
+ if (!pos || (end != NULL && end != pos))
+ return false;
+
+ return true;
+}
+
+// We need to extract the pci address of the device from the sysfs entry for the device like so:
+// $ readlink /sys/class/drm/card0
+// This should give you a path such as this for cards on the root bus:
+// /sys/devices/pci0000:00/0000:00:02.0/drm/card0
+// or something like this if there is a pci bridge:
+// /sys/devices/pci0000:00/0000:00:03.0/0000:01:01.0/0000:02:03.0/virtio2/drm/card0
+PciAddress* parse_pci_address_from_sysfs_path(const char* addr)
+{
+ char *pos = strstr(addr, "/pci");
+ if (!pos)
+ return NULL;
+
+ // advance to the numbers in pci0000:00
+ pos += 4;
+ int domain = read_next_hex_number(pos, ':', &pos);
+ if (!pos) {
+ return NULL;
+ }
+
+ // not used right now.
+ uint8_t bus = read_next_hex_number(pos + 1, '/', &pos);
+ if (!pos) {
+ return NULL;
+ }
+
+ PciAddress *address = pci_address_new();
+ address->domain = domain;
+ // now read all of the devices
+ for (int n = 0; ; n++) {
+ PciDevice *dev = g_new0(PciDevice, 1);
+ char *next = strchr(pos + 1, '/');
+ if (!parse_pci_device(pos + 1, next, dev)) {
+ g_free(dev);
+ break;
+ }
+ address->devices = g_list_append(address->devices, dev);
+ pos = next;
+ if (!pos)
+ break;
+ }
+ return address;
+}
+
+// format should be something like pci/$domain/$slot.$fn/$slot.$fn
+PciAddress* parse_pci_address_from_spice(char *input)
+{
+ const char * const prefix = "pci/";
+ if (strncmp(input, prefix, strlen(prefix)) != 0)
+ return NULL;
+
+ char *pos = input + strlen(prefix);
+ int domain = read_next_hex_number(pos, '/', &pos);
+ if (!pos) {
+ return NULL;
+ }
+
+ PciAddress *address = pci_address_new();
+ address->domain = domain;
+ // now read all of the devices
+ for (int n = 0; ; n++) {
+ PciDevice *dev = g_new0(PciDevice, 1);
+ char *next = strchr(pos + 1, '/');
+
+ dev->slot = read_next_hex_number(pos + 1, '.', &pos);
+ if (!pos) {
+ g_free(dev);
+ break;
+ }
+
+ dev->function = strtol(pos + 1, &pos, 16);
+ if (!pos || (next != NULL && next != pos)) {
+ g_free(dev);
+ break;
+ }
+
+ address->devices = g_list_append(address->devices, dev);
+ pos = next;
+ if (!pos)
+ break;
+ }
+ return address;
+}
+
+bool compare_addresses(PciAddress *a, PciAddress *b)
+{
+ // only check domain, slot, and function
+ if (!(a->domain == b->domain
+ && g_list_length(a->devices) == g_list_length(b->devices))) {
+ return false;
+ }
+
+ for (GList *la = a->devices, *lb = b->devices;
+ la != NULL;
+ la = la->next, lb = lb->next) {
+ PciDevice *deva = la->data;
+ PciDevice *devb = lb->data;
+
+ if (deva->slot != devb->slot
+ || deva->function != devb->function) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Connector type names from xorg modesetting driver
+static const char * const modesetting_output_names[] = {
+ [DRM_MODE_CONNECTOR_Unknown] = "None" ,
+ [DRM_MODE_CONNECTOR_VGA] = "VGA" ,
+ [DRM_MODE_CONNECTOR_DVII] = "DVI-I" ,
+ [DRM_MODE_CONNECTOR_DVID] = "DVI-D" ,
+ [DRM_MODE_CONNECTOR_DVIA] = "DVI-A" ,
+ [DRM_MODE_CONNECTOR_Composite] = "Composite" ,
+ [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO" ,
+ [DRM_MODE_CONNECTOR_LVDS] = "LVDS" ,
+ [DRM_MODE_CONNECTOR_Component] = "Component" ,
+ [DRM_MODE_CONNECTOR_9PinDIN] = "DIN" ,
+ [DRM_MODE_CONNECTOR_DisplayPort] = "DP" ,
+ [DRM_MODE_CONNECTOR_HDMIA] = "HDMI" ,
+ [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B" ,
+ [DRM_MODE_CONNECTOR_TV] = "TV" ,
+ [DRM_MODE_CONNECTOR_eDP] = "eDP" ,
+ [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual" ,
+ [DRM_MODE_CONNECTOR_DSI] = "DSI" ,
+ [DRM_MODE_CONNECTOR_DPI] = "DPI" ,
+};
+// Connector type names from qxl driver
+const char * const qxl_output_names[] = {
+ [DRM_MODE_CONNECTOR_Unknown] = "None" ,
+ [DRM_MODE_CONNECTOR_VGA] = "VGA" ,
+ [DRM_MODE_CONNECTOR_DVII] = "DVI" ,
+ [DRM_MODE_CONNECTOR_DVID] = "DVI" ,
+ [DRM_MODE_CONNECTOR_DVIA] = "DVI" ,
+ [DRM_MODE_CONNECTOR_Composite] = "Composite" ,
+ [DRM_MODE_CONNECTOR_SVIDEO] = "S-video" ,
+ [DRM_MODE_CONNECTOR_LVDS] = "LVDS" ,
+ [DRM_MODE_CONNECTOR_Component] = "CTV" ,
+ [DRM_MODE_CONNECTOR_9PinDIN] = "DIN" ,
+ [DRM_MODE_CONNECTOR_DisplayPort] = "DisplayPort" ,
+ [DRM_MODE_CONNECTOR_HDMIA] = "HDMI" ,
+ [DRM_MODE_CONNECTOR_HDMIB] = "HDMI" ,
+ [DRM_MODE_CONNECTOR_TV] = "TV" ,
+ [DRM_MODE_CONNECTOR_eDP] = "eDP" ,
+ [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual" ,
+};
+
+
+void drm_conn_name_full(drmModeConnector *conn, const char * const *names, int nnames, char *dest, size_t dlen)
+{
+ const char *type;
+
+ if (conn->connector_type_id < nnames &&
+ names[conn->connector_type]) {
+ type = names[conn->connector_type];
+ } else {
+ type = "unknown";
+ }
+ snprintf(dest, dlen, "%s-%d", type, conn->connector_type_id);
+}
+
+void drm_conn_name_qxl(drmModeConnector *conn, char *dest, size_t dlen)
+{
+ return drm_conn_name_full(conn, qxl_output_names,
+ sizeof(qxl_output_names)/sizeof(qxl_output_names[0]),
+ dest, dlen);
+}
+
+// FIXME: there are some cases (for example, in my Lenovo T460p laptop with
+// intel graphics) where the modesetting driver uses a name such as DP-3-1
+// instead of DP-4. These outputs are not likely to be common in virtual
+// machines, so it may not matter much (?)
+void drm_conn_name_modesetting(drmModeConnector *conn, char *dest, size_t dlen)
+{
+ return drm_conn_name_full(conn, modesetting_output_names,
+ sizeof(modesetting_output_names)/sizeof(modesetting_output_names[0]),
+ dest, dlen);
+}
+
+static const char *connections[] = {"", "*CONNECTED*", "disconnected", "unknown connection"};
+
+// verify that we parse the BDF notation correctly
+bool test_bdf(const char* string, int domain, uint8_t bus, uint8_t slot, uint8_t function)
+{
+ PciDevice pci_dev;
+ return (parse_pci_device(string, NULL, &pci_dev)
+ && (pci_dev.domain == domain)
+ && (pci_dev.bus == bus)
+ && (pci_dev.slot == slot)
+ && (pci_dev.function == function));
+}
+
+void test_bdf_parsing()
+{
+ // valid input
+ assert(test_bdf("0000:00:02.1", 0, 0, 2, 1));
+ assert(test_bdf("00:00:02.1", 0, 0, 2, 1));
+ assert(test_bdf("0000:00:03.0", 0, 0, 3, 0));
+ assert(test_bdf("0000:00:1d.1", 0, 0, 29, 1));
+ assert(test_bdf("0000:09:02.1", 0, 9, 2, 1));
+ assert(test_bdf("0000:1d:02.1", 0, 29, 2, 1));
+ assert(test_bdf("0000:00:02.d", 0, 0, 2, 13));
+ assert(test_bdf("000f:00:02.d", 15, 0, 2, 13));
+ assert(test_bdf("000f:00:02.d", 15, 0, 2, 13));
+ assert(test_bdf("000f:00:02.d", 15, 0, 2, 13));
+ assert(test_bdf("000f:00:02.d", 15, 0, 2, 13));
+ assert(test_bdf("ffff:ff:ff.f", 65535, 255, 255, 15));
+ assert(test_bdf("0:0:2.1", 0, 0, 2, 1));
+
+ // invalid input
+ assert(!test_bdf("0000:00:02:0", 0, 0, 0, 0));
+ assert(!test_bdf("-0001:00:02.1", 0, 0, 2, 1));
+ assert(!test_bdf("0000.00.02.0", 0, 0, 0, 0));
+ assert(!test_bdf("000f:00:02", 0, 0, 0, 0));
+ assert(!test_bdf("000f:00", 0, 0, 0, 0));
+ assert(!test_bdf("000f", 0, 0, 0, 0));
+ assert(!test_bdf("random string", 0, 0, 0, 0));
+ assert(!test_bdf("12345", 0, 0, 0, 0));
+}
+
+#define assert_device(dev, domain_, bus_, slot_, function_) \
+{ \
+ PciDevice* dev_ = (dev); \
+ assert(dev_ != NULL); \
+ assert(dev_->domain == domain_); \
+ assert(dev_->bus == bus_); \
+ assert(dev_->slot == slot_); \
+ assert(dev_->function == function_); \
+}
+
+void test_sysfs_parsing()
+{
+ PciAddress *addr = parse_pci_address_from_sysfs_path("../../devices/pci0000:00/0000:00:02.0/drm/card0");
+ assert(addr != NULL);
+ assert(addr->domain == 0);
+ assert(g_list_length(addr->devices) == 1);
+ assert_device(addr->devices->data, 0, 0, 2, 0);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_sysfs_path("../../devices/pciffff:ff/ffff:ff:ff.f/drm/card0");
+ assert(addr != NULL);
+ assert(addr->domain == 65535);
+ assert(g_list_length(addr->devices) == 1);
+ assert_device(addr->devices->data, 65535, 255, 255, 15);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_sysfs_path("../../devices/pci0000:00/0000:00:03.0/0000:01:01.0/0000:02:03.0/virtio2/drm/card0");
+ assert(addr != NULL);
+ assert(addr->domain == 0);
+ assert(g_list_length(addr->devices) == 3);
+ assert_device(addr->devices->data, 0, 0, 3, 0);
+ assert_device(addr->devices->next->data, 0, 1, 1, 0);
+ assert_device(addr->devices->next->next->data, 0, 2, 3, 0);
+ pci_address_free(addr);
+}
+
+void test_spice_parsing()
+{
+ PciAddress *addr = parse_pci_address_from_spice("pci/0000/02.0");
+ assert(addr != NULL);
+ assert(addr->domain == 0);
+ assert(g_list_length(addr->devices) == 1);
+ assert_device(addr->devices->data, 0, 0, 2, 0);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_spice("pci/ffff/ff.f");
+ assert(addr != NULL);
+ assert(addr->domain == 65535);
+ assert(g_list_length(addr->devices) == 1);
+ assert_device(addr->devices->data, 0, 0, 255, 15);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_spice("pci/0000/02.1/03.0");
+ assert(addr != NULL);
+ assert(addr->domain == 0);
+ assert(g_list_length(addr->devices) == 2);
+ assert_device(addr->devices->data, 0, 0, 2, 1);
+ assert_device(addr->devices->next->data, 0, 0, 3, 0);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_spice("pci/000a/01.0/02.1/03.0");
+ assert(addr != NULL);
+ assert(addr->domain == 10);
+ assert(g_list_length(addr->devices) == 3);
+ assert_device(addr->devices->data, 0, 0, 1, 0);
+ assert_device(addr->devices->next->data, 0, 0, 2, 1);
+ assert_device(addr->devices->next->next->data, 0, 0, 3, 0);
+ pci_address_free(addr);
+
+ addr = parse_pci_address_from_spice("pcx/0000/02.1/03.0");
+ assert(addr == NULL);
+
+ addr = parse_pci_address_from_spice("0000/02.0");
+ assert(addr == NULL);
+
+ addr = parse_pci_address_from_spice("0000/02.1/03.0");
+ assert(addr == NULL);
+}
+
+void test_compare_addresses()
+{
+ {
+ PciDevice da1 = {1, 0, 3, 0};
+ PciDevice da2 = {1, 1, 1, 0};
+ PciDevice da3 = {1, 2, 3, 0};
+ PciAddress a1 = {1, NULL};
+ a1.domain = 0;
+ a1.devices = g_list_append(a1.devices, &da1);
+ a1.devices = g_list_append(a1.devices, &da2);
+ a1.devices = g_list_append(a1.devices, &da3);
+
+ PciDevice db1 = {1, 0, 3, 0};
+ PciDevice db2 = {1, 1, 1, 0};
+ PciDevice db3 = {1, 2, 3, 0};
+ PciAddress a2 = {1, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+ a2.devices = g_list_append(a2.devices, &db2);
+ a2.devices = g_list_append(a2.devices, &db3);
+
+ assert(compare_addresses(&a1, &a2));
+ }
+ {
+ PciDevice da1 = {1, 0, 3, 0};
+ PciDevice da2 = {1, 1, 1, 0};
+ PciDevice da3 = {1, 2, 3, 0};
+ PciAddress a1 = {1, NULL};
+ a1.domain = 0;
+ a1.devices = g_list_append(a1.devices, &da1);
+ a1.devices = g_list_append(a1.devices, &da2);
+ a1.devices = g_list_append(a1.devices, &da3);
+
+ // a 'spice' format PCI address will not provide domain or bus for each
+ // device, only slot and function. So first two numbers for each device
+ // will always be set to 0
+ PciDevice db1 = {0, 0, 3, 0};
+ PciDevice db2 = {0, 0, 1, 0};
+ PciDevice db3 = {0, 0, 3, 0};
+ PciAddress a2 = {1, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+ a2.devices = g_list_append(a2.devices, &db2);
+ a2.devices = g_list_append(a2.devices, &db3);
+
+ assert(compare_addresses(&a1, &a2));
+ }
+ // different number of devices
+ {
+ PciDevice da1 = {0, 0, 3, 0};
+ PciDevice da2 = {0, 1, 1, 0};
+ PciDevice da3 = {0, 2, 3, 0};
+ PciAddress a1 = {0, NULL};
+ a1.domain = 0;
+ a1.devices = g_list_append(a1.devices, &da1);
+ a1.devices = g_list_append(a1.devices, &da2);
+ a1.devices = g_list_append(a1.devices, &da3);
+
+ PciDevice db1 = {0, 0, 3, 0};
+ PciDevice db2 = {0, 1, 1, 0};
+ PciAddress a2 = {0, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+ a2.devices = g_list_append(a2.devices, &db2);
+
+ assert(!compare_addresses(&a1, &a2));
+ }
+ // mismatched function
+ {
+ PciDevice da1 = {0, 0, 2, 0};
+ PciAddress a1 = {0, NULL};
+ a1.domain = 0;
+ a1.devices = g_list_append(a1.devices, &da1);
+
+ PciDevice db1 = {0, 0, 2, 1};
+ PciAddress a2 = {0, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+
+ assert(!compare_addresses(&a1, &a2));
+ }
+ // mismatched slot
+ {
+ PciDevice da1 = {0, 0, 2, 0};
+ PciAddress a1 = {0, NULL};
+ a1.domain = 0;
+ a1.devices = g_list_append(a1.devices, &da1);
+
+ PciDevice db1 = {0, 0, 1, 0};
+ PciAddress a2 = {0, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+
+ assert(!compare_addresses(&a1, &a2));
+ }
+ // mismatched domain
+ {
+ PciDevice da1 = {0, 0, 2, 0};
+ PciAddress a1 = {0, NULL};
+ a1.domain = 1;
+ a1.devices = g_list_append(a1.devices, &da1);
+
+ PciDevice db1 = {0, 0, 2, 0};
+ PciAddress a2 = {0, NULL};
+ a2.domain = 0;
+ a2.devices = g_list_append(a2.devices, &db1);
+
+ assert(!compare_addresses(&a1, &a2));
+ }
+}
+
+void run_tests()
+{
+ test_bdf_parsing();
+ test_sysfs_parsing();
+ test_spice_parsing();
+ test_compare_addresses();
+}
+
+#define PCI_VENDOR_ID_REDHAT 0x1b36
+#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 // virtio-gpu
+#define PCI_VENDOR_ID_INTEL 0x8086
+#define PCI_VENDOR_ID_NVIDIA 0x10de
+
+#define PCI_DEVICE_ID_QXL 0x0100
+#define PCI_DEVICE_ID_VIRTIO_GPU 0x1050
+
+static bool read_hex_value_from_file(const char *path, int* value)
+{
+ if (value == NULL || path == NULL)
+ return false;
+
+ int fd = open(path, O_RDONLY);
+ char buf[255];
+ bool result = false;
+ if (fd < 1)
+ return false;
+
+ size_t nread = 0, n = 0;
+ while (nread < sizeof(buf)
+ && (n = read(fd, buf + nread, sizeof(buf) - nread))) {
+ nread += n;
+ }
+ if (n < 0) {
+ goto cleanup;
+ }
+ buf[nread] = '\0';
+ char *endptr = NULL;
+ *value = strtol(buf, &endptr, 16);
+ // if we didn't read the entire string up to a newline, something went wrong.
+ if (endptr == buf || *endptr != '\n') {
+ goto cleanup;
+ }
+ result = true;
+
+cleanup:
+ //closing the fd here causes an abort...
+ //close(fd);
+ return result;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc < 3) {
+ if (argc == 2 && (strcmp(argv[1], "test") == 0)) {
+ run_tests();
+ printf("All tests succeeded\n");
+ return EXIT_SUCCESS;
+ }
+ errx(EXIT_FAILURE, "Usage: %s PCIADDR DEVICEDISPLAYID", argv[0]);
+ }
+ debug = (getenv("DEBUG") != NULL);
+
+ // PCI address should be in the following format:
+ // pci/$domain/$slot.$fn/$slot.$fn
+ PciAddress *user_pci_addr = parse_pci_address_from_spice(argv[1]);
+ if (!user_pci_addr) {
+ errx(EXIT_FAILURE, "Couldn't parse PCI address '%s'. Address should be the form 'pci/$domain/$slot.$fn/$slot.fn...", argv[1]);
+ }
+ int device_display_id = atoi(argv[2]);
+
+ // look up xrandr outputs
+ Display *xdisplay = XOpenDisplay(NULL);
+ if (!xdisplay) {
+ errx(EXIT_FAILURE, "Failed to open X dislay");
+ }
+
+ int rr_event_base, rr_error_base;
+ if (!XRRQueryExtension(xdisplay, &rr_event_base, &rr_error_base)) {
+ errx(EXIT_FAILURE, "Failed to initialize XRandr extension");
+ }
+
+ XRRScreenResources *xres = XRRGetScreenResourcesCurrent(xdisplay, DefaultRootWindow(xdisplay));
+ if (!xres) {
+ errx(EXIT_FAILURE, "Unable to get Xorg screen resources");
+ }
+
+ // Look for a device that matches the PCI address parsed above. Loop
+ // through the list of cards reported by the DRM subsytem
+ bool found_device = false;
+ for (int i = 0; i < 10; ++i) {
+ char dev_path[64];
+ struct stat buf;
+
+ // device node for the card is needed to access libdrm functionality
+ snprintf(dev_path, sizeof(dev_path), DRM_DEV_NAME, DRM_DIR_NAME, i);
+ if (stat(dev_path, &buf) != 0) {
+ // no card exists, exit loop
+ DEBUG("No card %i exists\n", i);
+ break;
+ }
+
+ // the sysfs directory for the card will allow us to determine the
+ // pci address for the device
+ char sys_path[64];
+ snprintf(sys_path, sizeof(sys_path), "/sys/class/drm/card%d", i);
+ DEBUG("sys path: %s\n", sys_path);
+
+ // the file /sys/class/drm/card0 is a symlink to a file that
+ // specifies the device's address. It usually points to something
+ // like /sys/devices/pci0000:00/0000:00:02.0/drm/card0
+ char device_link[PATH_MAX];
+ if (realpath(sys_path, device_link) == NULL) {
+ err(EXIT_FAILURE, "Failed to get the real path of %s", sys_path);
+ }
+ DEBUG("Device %s is at %s\n", dev_path, device_link);
+
+ PciAddress *drm_pci_addr = parse_pci_address_from_sysfs_path(device_link);
+ if (!drm_pci_addr) {
+ DEBUG("Can't determine pci address from '%s'", device_link);
+ continue;
+ }
+
+ if (compare_addresses(user_pci_addr, drm_pci_addr)) {
+ bool found_output = false;
+ char vendor_id_path[150], device_id_path[150];
+ snprintf(vendor_id_path, sizeof(vendor_id_path), "%s/device/vendor", sys_path);
+ int vendor_id = 0;
+ if (!read_hex_value_from_file(vendor_id_path, &vendor_id)) {
+ DEBUG("Unable to read vendor ID of card\n");
+ } else {
+ DEBUG("Vendor id of this card is 0x%x\n", vendor_id);
+ }
+ snprintf(device_id_path, sizeof(device_id_path), "%s/device/device", sys_path);
+ int device_id = 0;
+ if (!read_hex_value_from_file(device_id_path, &device_id)) {
+ DEBUG("Unable to read device ID of card\n");
+ } else {
+ DEBUG("Device id of this card is 0x%x\n", device_id);
+ }
+
+ int fd = open(dev_path, O_RDWR);
+ if (fd < 0) {
+ err(EXIT_FAILURE, "Unable to open file %s", dev_path);
+ }
+
+ drmModeResPtr res = drmModeGetResources(fd);
+ if (res) {
+ // find the drm output that is equal to device_display_id
+ if (device_display_id >= res->count_connectors) {
+ errx(EXIT_FAILURE, "Specified display id %i is higher than the maximum display id provided by this device (%i)",
+ device_display_id, res->count_connectors - 1);
+ }
+
+ drmModeConnectorPtr conn = drmModeGetConnector(fd, res->connectors[device_display_id]);
+ drmModeFreeResources(res);
+ res = NULL;
+
+ bool increment_xrandr_name = false;
+ if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_QXL) {
+ // Older QXL drivers numbered their outputs starting with
+ // 0. This contrasts with most drivers who start numbering
+ // outputs with 1. In this case, the output name will need
+ // to be incremented before comparing to the expected drm
+ // connector name
+ for (int i = 0; i < xres->noutput; ++i) {
+ XRROutputInfo *oinfo = XRRGetOutputInfo(xdisplay, xres, xres->outputs[i]);
+ // the QXL device doesn't use the drm names directly, but
+ // starts numbering from 0 instead of 1, so we need to
+ // detect this condition and add 1 to all output names in
+ // this scenario before comparing
+ if (i == 0) {
+ // check the first output to see if it ends with '-0'
+ char *postfix = oinfo->name + (strlen(oinfo->name) - 2);
+ if (strcmp(postfix, "-0") == 0) {
+ DEBUG("Need to increment xrandr name...\n");
+ increment_xrandr_name = true;
+ break;
+ }
+ }
+ XRRFreeOutputInfo(oinfo);
+ }
+ }
+ // Compare the name of the xrandr output against what we would
+ // expect based on the drm connection type. The xrandr names
+ // are driver-specific, so we need to special-case some
+ // drivers. Most hardware these days uses the 'modesetting'
+ // driver, but the QXL device uses its own driver which has
+ // different naming conventions
+ char expected_name[100];
+ if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_QXL) {
+ drm_conn_name_qxl(conn, expected_name, sizeof(expected_name));
+ } else {
+ drm_conn_name_modesetting(conn, expected_name, sizeof(expected_name));
+ }
+
+ // Loop through xrandr outputs and check whether the xrandr
+ // output name matches the drm connector name
+ for (int i = 0; i < xres->noutput; ++i) {
+ int oid = xres->outputs[i];
+ XRROutputInfo *oinfo = XRRGetOutputInfo(xdisplay, xres, oid);
+ char xname[100];
+ // NOTE: this will increment the counter for every XRandr
+ // output, not only the ones associated with the current
+ // device. This should only matter in the unlikely scenario
+ // where X is configured with multiple devices and the
+ // incremented name of an output on another device
+ // conflicts with the name of the output we're looking for,
+ if (increment_xrandr_name) {
+ DEBUG("Original xrandr name is %s\n", oinfo->name);
+ char *id_pos = strrchr(oinfo->name, '-') + 1;
+ size_t prefix_len = id_pos - oinfo->name;
+ memcpy(xname, oinfo->name, prefix_len);
+ int newid = atoi(id_pos) + 1;
+ DEBUG("New id suffix should be -%i\n", newid);
+ int sz = snprintf(xname + prefix_len, sizeof(xname - prefix_len), "%i", newid);
+ if (sz < 0) {
+ errx(EXIT_FAILURE, "Unable to increment name");
+ }
+ xname[prefix_len + sz] = '\0';
+ } else {
+ strncpy(xname, oinfo->name, sizeof(xname) - 1);
+ xname[sizeof(xname)] = '\0';
+ }
+
+ DEBUG("Checking whether xrandr output %i (%s) matches expected name %s\n", i, xname, expected_name);
+ if (strcmp(xname, expected_name) == 0) {
+ found_output = true;
+ printf(" Found matching X Output: name=%s id=%i (%s)\n",
+ oinfo->name,
+ (int)oid,
+ connections[oinfo->connection + 1]);
+ XRRFreeOutputInfo(oinfo);
+ break;
+ }
+ XRRFreeOutputInfo(oinfo);
+ }
+ drmModeFreeConnector(conn);
+ conn = NULL;
+ } else {
+ if (vendor_id == PCI_VENDOR_ID_NVIDIA) {
+ DEBUG("This device is an Nvidia device that doesn't provide information about drm connectors. We'll simply look up the XRandr output with index %i\n", device_display_id);
+ // the proprietary nvidia driver does not provide outputs
+ // via drm, so the only thing we can do is just assume that
+ // it is the only device assigned to X, and use the xrandr
+ // output order to determine the proper display.
+ if (device_display_id >= xres->noutput) {
+ errx(EXIT_FAILURE, "The device display id %i does not exist", device_display_id);
+ }
+ int oid = xres->outputs[device_display_id];
+ XRROutputInfo *oinfo = XRRGetOutputInfo(xdisplay, xres, oid);
+ printf(" Found matching X Output: name=%s id=%i (%s)\n",
+ oinfo->name,
+ (int)oid,
+ connections[oinfo->connection + 1]);
+ XRRFreeOutputInfo(oinfo);
+ found_output = true;
+ } else {
+ errx(EXIT_FAILURE, "Unable to get DRM resources for card");
+ }
+ }
+
+
+ if (!found_output) {
+ errx(EXIT_FAILURE, "Couldn't find an XRandr output for the specified device");
+ }
+
+ // we found the right card, abort the loop
+ found_device = true;
+ break;
+ } else {
+ DEBUG("card addr '%s' does not match requested addr '%s'\n", device_link, argv[1]);
+ }
+ }
+ XRRFreeScreenResources(xres);
+ xres = NULL;
+
+ if (!found_device) {
+ errx(EXIT_FAILURE, "Couldn't find a card with the given PCI address");
+ }
+ return EXIT_SUCCESS;
+}
--
2.17.2
More information about the Spice-devel
mailing list