Fix minor spelling errors.
[vd_agent.git] / src / vdagent / device-info.c
blob2e5e91fd1fadce701bbe526d7b8319b7701ef881
1 /* device-info.c utility function for looking up the xrandr output id for a
2 * given device address and display id
4 * Copyright 2018 Red Hat, Inc.
6 * Red Hat Authors:
7 * Jonathon Jongsma <jjongsma@redhat.com>
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <assert.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
30 #include <syslog.h>
31 #include <xf86drm.h>
32 #include <xf86drmMode.h>
33 #include <unistd.h>
34 #include <X11/extensions/Xrandr.h>
35 #include <glib.h>
37 #include "device-info.h"
39 #define PCI_VENDOR_ID_REDHAT 0x1b36
40 #define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 // virtio-gpu
41 #define PCI_VENDOR_ID_INTEL 0x8086
42 #define PCI_VENDOR_ID_NVIDIA 0x10de
44 #define PCI_DEVICE_ID_QXL 0x0100
45 #define PCI_DEVICE_ID_VIRTIO_GPU 0x1050
47 typedef struct PciDevice {
48 int domain;
49 uint8_t bus;
50 uint8_t slot;
51 uint8_t function;
52 } PciDevice;
54 typedef struct PciAddress {
55 int domain;
56 GList *devices; /* PciDevice */
57 } PciAddress;
59 static PciAddress* pci_address_new()
61 return g_new0(PciAddress, 1);
64 static void pci_address_free(PciAddress *addr)
66 g_list_free_full(addr->devices, g_free);
67 g_free(addr);
71 static int read_next_hex_number(const char *input, char delim, char **endptr)
73 assert(input != NULL);
74 assert(endptr != NULL);
76 const char *pos = strchr(input, delim);
77 int n;
78 if (!pos) {
79 *endptr = NULL;
80 return 0;
83 char *endpos;
84 n = strtol(input, &endpos, 16);
86 // check if we read all characters until the delimiter
87 if (endpos != pos) {
88 endpos = NULL;
91 *endptr = endpos;
92 return n;
95 // the device should be specified in BDF notation (e.g. 0000:00:02.0)
96 // see https://wiki.xen.org/wiki/Bus:Device.Function_(BDF)_Notation
97 static bool parse_pci_device(const char *bdf, const char *end, PciDevice *device)
99 if (!end) {
100 end = strchr(bdf, 0);
103 int endpos = -1;
104 int domain, bus, slot, function;
105 sscanf(bdf, "%x:%x:%x.%x%n", &domain, &bus, &slot, &function, &endpos);
106 if (!device || endpos < 0 || bdf + endpos != end) {
107 return false;
109 if (domain < 0 || bus < 0 || slot < 0 || function < 0) {
110 return false;
113 device->domain = domain;
114 device->bus = bus;
115 device->slot = slot;
116 device->function = function;
117 return true;
120 // We need to extract the pci address of the device from the sysfs entry for the device like so:
121 // $ readlink /sys/class/drm/card0
122 // This should give you a path such as this for cards on the root bus:
123 // /sys/devices/pci0000:00/0000:00:02.0/drm/card0
124 // or something like this if there is a pci bridge:
125 // /sys/devices/pci0000:00/0000:00:03.0/0000:01:01.0/0000:02:03.0/virtio2/drm/card0
126 static PciAddress* parse_pci_address_from_sysfs_path(const char* addr)
128 char *pos = strstr(addr, "/pci");
129 if (!pos) {
130 return NULL;
133 // advance to the numbers in pci0000:00
134 pos += 4;
135 int domain = read_next_hex_number(pos, ':', &pos);
136 if (!pos) {
137 return NULL;
140 // not used right now.
141 G_GNUC_UNUSED uint8_t bus = read_next_hex_number(pos + 1, '/', &pos);
142 if (!pos) {
143 return NULL;
146 PciAddress *address = pci_address_new();
147 address->domain = domain;
148 // now read all of the devices
149 while (pos) {
150 PciDevice *dev = g_new0(PciDevice, 1);
151 char *next = strchr(pos + 1, '/');
152 if (!parse_pci_device(pos + 1, next, dev)) {
153 g_free(dev);
154 break;
156 address->devices = g_list_append(address->devices, dev);
157 pos = next;
159 return address;
162 // format should be something like pci/$domain/$slot.$fn/$slot.$fn
163 static PciAddress* parse_pci_address_from_spice(char *input)
165 static const char prefix[] = "pci/";
166 if (strncmp(input, prefix, strlen(prefix)) != 0) {
167 return NULL;
170 char *pos = input + strlen(prefix);
171 int domain = read_next_hex_number(pos, '/', &pos);
172 if (!pos) {
173 return NULL;
176 PciAddress *address = pci_address_new();
177 address->domain = domain;
178 // now read all of the devices
179 for (int n = 0; ; n++) {
180 PciDevice *dev = g_new0(PciDevice, 1);
181 char *next = strchr(pos + 1, '/');
183 dev->slot = read_next_hex_number(pos + 1, '.', &pos);
184 if (!pos) {
185 g_free(dev);
186 break;
189 dev->function = strtol(pos + 1, &pos, 16);
190 if (!pos || (next != NULL && next != pos)) {
191 g_free(dev);
192 break;
195 address->devices = g_list_append(address->devices, dev);
196 pos = next;
197 if (!pos) {
198 break;
201 return address;
204 static bool compare_addresses(PciAddress *a, PciAddress *b)
206 // only check domain, slot, and function
207 if (a->domain != b->domain) {
208 return false;
211 const GList *la, *lb;
212 for (la = a->devices, lb = b->devices;
213 la != NULL && lb != NULL;
214 la = la->next, lb = lb->next) {
215 PciDevice *deva = la->data;
216 PciDevice *devb = lb->data;
218 if (deva->slot != devb->slot
219 || deva->function != devb->function) {
220 return false;
224 /* True only if both have the same length */
225 return (la == NULL && lb == NULL);
228 // Connector type names from xorg modesetting driver
229 static const char * const modesetting_output_names[] = {
230 [DRM_MODE_CONNECTOR_Unknown] = "None" ,
231 [DRM_MODE_CONNECTOR_VGA] = "VGA" ,
232 [DRM_MODE_CONNECTOR_DVII] = "DVI-I" ,
233 [DRM_MODE_CONNECTOR_DVID] = "DVI-D" ,
234 [DRM_MODE_CONNECTOR_DVIA] = "DVI-A" ,
235 [DRM_MODE_CONNECTOR_Composite] = "Composite" ,
236 [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO" ,
237 [DRM_MODE_CONNECTOR_LVDS] = "LVDS" ,
238 [DRM_MODE_CONNECTOR_Component] = "Component" ,
239 [DRM_MODE_CONNECTOR_9PinDIN] = "DIN" ,
240 [DRM_MODE_CONNECTOR_DisplayPort] = "DP" ,
241 [DRM_MODE_CONNECTOR_HDMIA] = "HDMI" ,
242 [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B" ,
243 [DRM_MODE_CONNECTOR_TV] = "TV" ,
244 [DRM_MODE_CONNECTOR_eDP] = "eDP" ,
245 [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual" ,
246 [DRM_MODE_CONNECTOR_DSI] = "DSI" ,
247 [DRM_MODE_CONNECTOR_DPI] = "DPI" ,
249 // Connector type names from qxl driver
250 static const char * const qxl_output_names[] = {
251 [DRM_MODE_CONNECTOR_Unknown] = "None" ,
252 [DRM_MODE_CONNECTOR_VGA] = "VGA" ,
253 [DRM_MODE_CONNECTOR_DVII] = "DVI" ,
254 [DRM_MODE_CONNECTOR_DVID] = "DVI" ,
255 [DRM_MODE_CONNECTOR_DVIA] = "DVI" ,
256 [DRM_MODE_CONNECTOR_Composite] = "Composite" ,
257 [DRM_MODE_CONNECTOR_SVIDEO] = "S-video" ,
258 [DRM_MODE_CONNECTOR_LVDS] = "LVDS" ,
259 [DRM_MODE_CONNECTOR_Component] = "CTV" ,
260 [DRM_MODE_CONNECTOR_9PinDIN] = "DIN" ,
261 [DRM_MODE_CONNECTOR_DisplayPort] = "DisplayPort" ,
262 [DRM_MODE_CONNECTOR_HDMIA] = "HDMI" ,
263 [DRM_MODE_CONNECTOR_HDMIB] = "HDMI" ,
264 [DRM_MODE_CONNECTOR_TV] = "TV" ,
265 [DRM_MODE_CONNECTOR_eDP] = "eDP" ,
266 [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual" ,
270 static void drm_conn_name_full(drmModeConnector *conn,
271 const char * const *names,
272 int nnames, char *dest,
273 size_t dlen, bool decrement_id)
275 const char *type;
277 if (conn->connector_type < nnames &&
278 names[conn->connector_type]) {
279 type = names[conn->connector_type];
280 } else {
281 type = "unknown";
284 uint8_t id = conn->connector_type_id;
285 if (decrement_id) {
286 id--;
288 snprintf(dest, dlen, "%s-%d", type, id);
291 static void drm_conn_name_qxl(drmModeConnector *conn, char *dest, size_t dlen, bool decrement_id)
293 return drm_conn_name_full(conn, qxl_output_names,
294 sizeof(qxl_output_names)/sizeof(qxl_output_names[0]),
295 dest, dlen, decrement_id);
298 // NOTE: there are some cases (for example, in a Lenovo T460p laptop with
299 // intel graphics when attached to a docking station) where the modesetting
300 // driver uses a name such as DP-3-1 instead of DP-4. These outputs are not
301 // likely to exist in virtual machines, so they shouldn't matter much
302 static void drm_conn_name_modesetting(drmModeConnector *conn, char *dest, size_t dlen)
304 return drm_conn_name_full(conn, modesetting_output_names,
305 sizeof(modesetting_output_names)/sizeof(modesetting_output_names[0]),
306 dest, dlen, false);
309 static bool read_hex_value_from_file(const char *path, int* value)
311 if (value == NULL || path == NULL) {
312 return false;
315 FILE *f = fopen(path, "r");
316 if (f == NULL) {
317 return false;
320 int endpos = -1;
321 bool result = (fscanf(f, "%x\n%n", value, &endpos) > 0 && endpos >= 0);
323 fclose(f);
324 return result;
327 // returns a path to a drm device found at the given PCI Address. Returned
328 // string must be freed by caller.
329 static char* find_device_at_pci_address(PciAddress *pci_addr, int *vendor_id, int *device_id)
331 g_return_val_if_fail(pci_addr != NULL, NULL);
332 g_return_val_if_fail(device_id != NULL, NULL);
333 g_return_val_if_fail(vendor_id != NULL, NULL);
334 // Look for a device that matches the PCI address parsed above. Loop
335 // through the list of cards reported by the DRM subsystem
336 for (int i = 0; i < 10; ++i) {
337 char dev_path[64];
338 struct stat buf;
340 // device node for the card is needed to access libdrm functionality
341 snprintf(dev_path, sizeof(dev_path), DRM_DEV_NAME, DRM_DIR_NAME, i);
342 if (stat(dev_path, &buf) != 0) {
343 // no card exists, exit loop
344 syslog(LOG_DEBUG, "card%i not found while listing DRM devices.", i);
345 break;
348 // the sysfs directory for the card will allow us to determine the
349 // pci address for the device
350 char sys_path[64];
351 snprintf(sys_path, sizeof(sys_path), "/sys/class/drm/card%d", i);
353 // the file /sys/class/drm/card0 is a symlink to a file that
354 // specifies the device's address. It usually points to something
355 // like /sys/devices/pci0000:00/0000:00:02.0/drm/card0
356 char device_link[PATH_MAX];
357 if (realpath(sys_path, device_link) == NULL) {
358 syslog(LOG_WARNING, "Failed to get the real path of %s", sys_path);
359 break;
361 syslog(LOG_DEBUG, "Device %s is at %s", dev_path, device_link);
363 PciAddress *drm_pci_addr = parse_pci_address_from_sysfs_path(device_link);
364 if (!drm_pci_addr) {
365 syslog(LOG_WARNING, "Can't determine pci address from '%s'", device_link);
366 continue;
369 if (!compare_addresses(pci_addr, drm_pci_addr)) {
370 pci_address_free(drm_pci_addr);
371 continue;
373 pci_address_free(drm_pci_addr);
374 char id_path[150];
375 snprintf(id_path, sizeof(id_path), "%s/device/vendor", sys_path);
376 if (!read_hex_value_from_file(id_path, vendor_id)) {
377 syslog(LOG_WARNING, "Unable to read vendor ID of card: %s", strerror(errno));
379 snprintf(id_path, sizeof(id_path), "%s/device/device", sys_path);
380 if (!read_hex_value_from_file(id_path, device_id)) {
381 syslog(LOG_WARNING, "Unable to read device ID of card: %s", strerror(errno));
384 syslog(LOG_DEBUG, "Found card '%s' with Vendor ID %#x, Device ID %#x",
385 device_link, *device_id, *vendor_id);
386 return g_strdup(dev_path);
388 return NULL;
393 * Look up DRM info for the device, and retrieve the expected connector name.
394 * This name will later be compared to the monitor names found at the display manager level.
396 int get_connector_name_for_device_info(VDAgentDeviceDisplayInfo *device_info,
397 char *expected_name, size_t name_size,
398 bool has_virtual_zero_display)
400 PciAddress *user_pci_addr = parse_pci_address_from_spice((char*)device_info->device_address);
401 if (!user_pci_addr) {
402 syslog(LOG_WARNING,
403 "Couldn't parse PCI address '%s'. "
404 "Address should be the form 'pci/$domain/$slot.$fn/$slot.fn...",
405 device_info->device_address);
406 return -1;
409 int vendor_id = 0;
410 int device_id = 0;
411 char *dev_path = find_device_at_pci_address(user_pci_addr, &vendor_id, &device_id);
412 pci_address_free(user_pci_addr);
414 int drm_fd = open(dev_path, O_RDWR);
415 if (drm_fd < 0) {
416 syslog(LOG_WARNING, "Unable to open file %s", dev_path);
417 g_free(dev_path);
418 return -1;
421 drmModeResPtr res = drmModeGetResources(drm_fd);
422 if (res == NULL) {
423 syslog(LOG_WARNING,
424 "Unable to get DRM resources for card %s. "
425 "Falling back to using xrandr output index.",
426 dev_path);
427 close(drm_fd);
428 g_free(dev_path);
429 return 1; // error out - actual handling is deferred to the caller
432 // no need for dev_path anymore
433 g_free(dev_path);
435 // find the drm output that is equal to device_display_id
436 if (device_info->device_display_id >= res->count_connectors) {
437 syslog(LOG_WARNING,
438 "Specified display id %i is higher than the maximum display id "
439 "provided by this device (%i)",
440 device_info->device_display_id, res->count_connectors - 1);
441 close(drm_fd);
442 return -1;
445 drmModeConnectorPtr conn =
446 drmModeGetConnector(drm_fd, res->connectors[device_info->device_display_id]);
447 drmModeFreeResources(res);
448 res = NULL;
449 close(drm_fd);
451 if (conn == NULL) {
452 syslog(LOG_WARNING, "Unable to get drm connector for display id %i",
453 device_info->device_display_id);
454 return -1;
457 bool decrement_name = false;
459 if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_QXL
460 && has_virtual_zero_display) {
461 decrement_name = true;
464 // Compare the name of the xrandr output against what we would
465 // expect based on the drm connection type. The xrandr names
466 // are driver-specific, so we need to special-case some
467 // drivers. Most hardware these days uses the 'modesetting'
468 // driver, but the QXL device uses its own driver which has
469 // different naming conventions
470 if (vendor_id == PCI_VENDOR_ID_REDHAT && device_id == PCI_DEVICE_ID_QXL) {
471 drm_conn_name_qxl(conn, expected_name, name_size, decrement_name);
472 } else {
473 drm_conn_name_modesetting(conn, expected_name, name_size);
475 drmModeFreeConnector(conn);
477 return 0;
480 // PCI address should be in the following format:
481 // pci/$domain/$slot.$fn/$slot.$fn
482 bool lookup_xrandr_output_for_device_info(VDAgentDeviceDisplayInfo *device_info,
483 Display *xdisplay,
484 XRRScreenResources *xres,
485 RROutput *output_id,
486 bool has_virtual_zero_display)
488 char expected_name[100];
489 int ret;
491 ret = get_connector_name_for_device_info(device_info, expected_name, sizeof(expected_name),
492 has_virtual_zero_display);
493 switch (ret) {
494 case -1: // generic error => exit
495 return false;
496 case 0:
497 // Loop through xrandr outputs and check whether the xrandr
498 // output name matches the drm connector name
499 for (int i = 0; i < xres->noutput; ++i) {
500 int oid = xres->outputs[i];
501 XRROutputInfo *oinfo = XRRGetOutputInfo(xdisplay, xres, oid);
502 if (!oinfo) {
503 syslog(LOG_WARNING, "Unable to lookup XRandr output info for output %i", oid);
504 return false;
507 if (strcmp(oinfo->name, expected_name) == 0) {
508 *output_id = oid;
509 syslog(LOG_DEBUG, "Found matching X Output: name=%s id=%i",
510 oinfo->name, (int)oid);
511 XRRFreeOutputInfo(oinfo);
512 return true;
514 XRRFreeOutputInfo(oinfo);
516 break;
517 case 1: // no DRM info found
518 // This is probably a proprietary driver (e.g. Nvidia) that does
519 // not provide outputs via drm, so the only thing we can do is just
520 // assume that it is the only device assigned to X, and use the
521 // xrandr output order to determine the proper display.
522 if (device_info->device_display_id >= xres->noutput) {
523 syslog(LOG_WARNING, "The device display id %i does not exist",
524 device_info->device_display_id);
525 return false;
527 *output_id = xres->outputs[device_info->device_display_id];
528 return true;
531 syslog(LOG_WARNING, "Couldn't find an XRandr output for the specified device");
532 return false;