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.
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/>.
32 #include <xf86drmMode.h>
34 #include <X11/extensions/Xrandr.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
{
54 typedef struct PciAddress
{
56 GList
*devices
; /* PciDevice */
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
);
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
);
84 n
= strtol(input
, &endpos
, 16);
86 // check if we read all characters until the delimiter
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
)
100 end
= strchr(bdf
, 0);
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
) {
109 if (domain
< 0 || bus
< 0 || slot
< 0 || function
< 0) {
113 device
->domain
= domain
;
116 device
->function
= function
;
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");
133 // advance to the numbers in pci0000:00
135 int domain
= read_next_hex_number(pos
, ':', &pos
);
140 // not used right now.
141 G_GNUC_UNUSED
uint8_t bus
= read_next_hex_number(pos
+ 1, '/', &pos
);
146 PciAddress
*address
= pci_address_new();
147 address
->domain
= domain
;
148 // now read all of the devices
150 PciDevice
*dev
= g_new0(PciDevice
, 1);
151 char *next
= strchr(pos
+ 1, '/');
152 if (!parse_pci_device(pos
+ 1, next
, dev
)) {
156 address
->devices
= g_list_append(address
->devices
, dev
);
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) {
170 char *pos
= input
+ strlen(prefix
);
171 int domain
= read_next_hex_number(pos
, '/', &pos
);
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
);
189 dev
->function
= strtol(pos
+ 1, &pos
, 16);
190 if (!pos
|| (next
!= NULL
&& next
!= pos
)) {
195 address
->devices
= g_list_append(address
->devices
, dev
);
204 static bool compare_addresses(PciAddress
*a
, PciAddress
*b
)
206 // only check domain, slot, and function
207 if (a
->domain
!= b
->domain
) {
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
) {
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
)
277 if (conn
->connector_type
< nnames
&&
278 names
[conn
->connector_type
]) {
279 type
= names
[conn
->connector_type
];
284 uint8_t id
= conn
->connector_type_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]),
309 static bool read_hex_value_from_file(const char *path
, int* value
)
311 if (value
== NULL
|| path
== NULL
) {
315 FILE *f
= fopen(path
, "r");
321 bool result
= (fscanf(f
, "%x\n%n", value
, &endpos
) > 0 && endpos
>= 0);
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
) {
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
);
348 // the sysfs directory for the card will allow us to determine the
349 // pci address for the device
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
);
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
);
365 syslog(LOG_WARNING
, "Can't determine pci address from '%s'", device_link
);
369 if (!compare_addresses(pci_addr
, drm_pci_addr
)) {
370 pci_address_free(drm_pci_addr
);
373 pci_address_free(drm_pci_addr
);
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
);
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
) {
403 "Couldn't parse PCI address '%s'. "
404 "Address should be the form 'pci/$domain/$slot.$fn/$slot.fn...",
405 device_info
->device_address
);
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
);
416 syslog(LOG_WARNING
, "Unable to open file %s", dev_path
);
421 drmModeResPtr res
= drmModeGetResources(drm_fd
);
424 "Unable to get DRM resources for card %s. "
425 "Falling back to using xrandr output index.",
429 return 1; // error out - actual handling is deferred to the caller
432 // no need for dev_path anymore
435 // find the drm output that is equal to device_display_id
436 if (device_info
->device_display_id
>= res
->count_connectors
) {
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);
445 drmModeConnectorPtr conn
=
446 drmModeGetConnector(drm_fd
, res
->connectors
[device_info
->device_display_id
]);
447 drmModeFreeResources(res
);
452 syslog(LOG_WARNING
, "Unable to get drm connector for display id %i",
453 device_info
->device_display_id
);
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
);
473 drm_conn_name_modesetting(conn
, expected_name
, name_size
);
475 drmModeFreeConnector(conn
);
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
,
484 XRRScreenResources
*xres
,
486 bool has_virtual_zero_display
)
488 char expected_name
[100];
491 ret
= get_connector_name_for_device_info(device_info
, expected_name
, sizeof(expected_name
),
492 has_virtual_zero_display
);
494 case -1: // generic error => exit
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
);
503 syslog(LOG_WARNING
, "Unable to lookup XRandr output info for output %i", oid
);
507 if (strcmp(oinfo
->name
, expected_name
) == 0) {
509 syslog(LOG_DEBUG
, "Found matching X Output: name=%s id=%i",
510 oinfo
->name
, (int)oid
);
511 XRRFreeOutputInfo(oinfo
);
514 XRRFreeOutputInfo(oinfo
);
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
);
527 *output_id
= xres
->outputs
[device_info
->device_display_id
];
531 syslog(LOG_WARNING
, "Couldn't find an XRandr output for the specified device");