> > 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. In title and comment "monitor ID" -> "device display ID". I would add some explanation on the comment like how this is supposed to be used. I did some changes, not much functional beside some QXL detection and removal of some issue, see https://cgit.freedesktop.org/~fziglio/vd_agent_linux/log/?h=jj, you probably can squash them all. > --- > > 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@xxxxxxxxxx> > + * > + * 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); I don't think a big issue but this will accept numbers like " -0x123a" or "21287531273521751834213421312214215425328513725" (overflow), but should fail later so not a big issue if somebody wants to pass garbage. > + > + // 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; there is a mix in the code of returning failure, ignore or asserting for NULL pointers. > + > + 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); I think this is due to the overflow in the main function. > + 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) { Why only 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; > +} Frediano _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel