loader.c revision 01e04c3f
1/*
2 * Copyright (C) 2013 Rob Clark <robclark@freedesktop.org>
3 * Copyright (C) 2014-2016 Emil Velikov <emil.l.velikov@gmail.com>
4 * Copyright (C) 2016 Intel Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * Authors:
26 *    Rob Clark <robclark@freedesktop.org>
27 */
28
29#include <errno.h>
30#include <fcntl.h>
31#include <sys/stat.h>
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdbool.h>
35#include <string.h>
36#include <unistd.h>
37#include <stdlib.h>
38#ifdef MAJOR_IN_MKDEV
39#include <sys/mkdev.h>
40#endif
41#ifdef MAJOR_IN_SYSMACROS
42#include <sys/sysmacros.h>
43#endif
44#include "loader.h"
45
46#ifdef HAVE_LIBDRM
47#include <xf86drm.h>
48#ifdef USE_DRICONF
49#include "util/xmlconfig.h"
50#include "util/xmlpool.h"
51#endif
52#endif
53
54#define __IS_LOADER
55#include "pci_id_driver_map.h"
56
57static void default_logger(int level, const char *fmt, ...)
58{
59   if (level <= _LOADER_WARNING) {
60      va_list args;
61      va_start(args, fmt);
62      vfprintf(stderr, fmt, args);
63      va_end(args);
64   }
65}
66
67static void (*log_)(int level, const char *fmt, ...) = default_logger;
68
69int
70loader_open_device(const char *device_name)
71{
72   int fd;
73#ifdef O_CLOEXEC
74   fd = open(device_name, O_RDWR | O_CLOEXEC);
75   if (fd == -1 && errno == EINVAL)
76#endif
77   {
78      fd = open(device_name, O_RDWR);
79      if (fd != -1)
80         fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
81   }
82   return fd;
83}
84
85static char *loader_get_kernel_driver_name(int fd)
86{
87#if HAVE_LIBDRM
88   char *driver;
89   drmVersionPtr version = drmGetVersion(fd);
90
91   if (!version) {
92      log_(_LOADER_WARNING, "failed to get driver name for fd %d\n", fd);
93      return NULL;
94   }
95
96   driver = strndup(version->name, version->name_len);
97
98   drmFreeVersion(version);
99   return driver;
100#else
101   return NULL;
102#endif
103}
104
105#if defined(HAVE_LIBDRM)
106int
107loader_open_render_node(const char *name)
108{
109   drmDevicePtr *devices, device;
110   int err, render = -ENOENT, fd;
111   unsigned int num, i;
112
113   err = drmGetDevices2(0, NULL, 0);
114   if (err < 0)
115      return err;
116
117   num = err;
118
119   devices = calloc(num, sizeof(*devices));
120   if (!devices)
121      return -ENOMEM;
122
123   err = drmGetDevices2(0, devices, num);
124   if (err < 0) {
125      render = err;
126      goto free;
127   }
128
129   for (i = 0; i < num; i++) {
130      device = devices[i];
131
132      if ((device->available_nodes & (1 << DRM_NODE_RENDER)) &&
133          (device->bustype == DRM_BUS_PLATFORM)) {
134         drmVersionPtr version;
135
136         fd = open(device->nodes[DRM_NODE_RENDER], O_RDWR | O_CLOEXEC);
137         if (fd < 0)
138            continue;
139
140         version = drmGetVersion(fd);
141         if (!version) {
142            close(fd);
143            continue;
144         }
145
146         if (strcmp(version->name, name) != 0) {
147            drmFreeVersion(version);
148            close(fd);
149            continue;
150         }
151
152         drmFreeVersion(version);
153         render = fd;
154         break;
155      }
156   }
157
158   drmFreeDevices(devices, num);
159
160free:
161   free(devices);
162   return render;
163}
164
165#ifdef USE_DRICONF
166static const char __driConfigOptionsLoader[] =
167DRI_CONF_BEGIN
168    DRI_CONF_SECTION_INITIALIZATION
169        DRI_CONF_DEVICE_ID_PATH_TAG()
170        DRI_CONF_DRI_DRIVER()
171    DRI_CONF_SECTION_END
172DRI_CONF_END;
173
174static char *loader_get_dri_config_driver(int fd)
175{
176   driOptionCache defaultInitOptions;
177   driOptionCache userInitOptions;
178   char *dri_driver = NULL;
179   char *kernel_driver = loader_get_kernel_driver_name(fd);
180
181   driParseOptionInfo(&defaultInitOptions, __driConfigOptionsLoader);
182   driParseConfigFiles(&userInitOptions, &defaultInitOptions, 0,
183                       "loader", kernel_driver);
184   if (driCheckOption(&userInitOptions, "dri_driver", DRI_STRING)) {
185      char *opt = driQueryOptionstr(&userInitOptions, "dri_driver");
186      /* not an empty string */
187      if (*opt)
188         dri_driver = strdup(opt);
189   }
190   driDestroyOptionCache(&userInitOptions);
191   driDestroyOptionInfo(&defaultInitOptions);
192
193   free(kernel_driver);
194   return dri_driver;
195}
196
197static char *loader_get_dri_config_device_id(void)
198{
199   driOptionCache defaultInitOptions;
200   driOptionCache userInitOptions;
201   char *prime = NULL;
202
203   driParseOptionInfo(&defaultInitOptions, __driConfigOptionsLoader);
204   driParseConfigFiles(&userInitOptions, &defaultInitOptions, 0, "loader", NULL);
205   if (driCheckOption(&userInitOptions, "device_id", DRI_STRING))
206      prime = strdup(driQueryOptionstr(&userInitOptions, "device_id"));
207   driDestroyOptionCache(&userInitOptions);
208   driDestroyOptionInfo(&defaultInitOptions);
209
210   return prime;
211}
212#endif
213
214static char *drm_construct_id_path_tag(drmDevicePtr device)
215{
216   char *tag = NULL;
217
218   if (device->bustype == DRM_BUS_PCI) {
219      if (asprintf(&tag, "pci-%04x_%02x_%02x_%1u",
220                   device->businfo.pci->domain,
221                   device->businfo.pci->bus,
222                   device->businfo.pci->dev,
223                   device->businfo.pci->func) < 0) {
224         return NULL;
225      }
226   } else if (device->bustype == DRM_BUS_PLATFORM ||
227              device->bustype == DRM_BUS_HOST1X) {
228      char *fullname, *name, *address;
229
230      if (device->bustype == DRM_BUS_PLATFORM)
231         fullname = device->businfo.platform->fullname;
232      else
233         fullname = device->businfo.host1x->fullname;
234
235      name = strrchr(fullname, '/');
236      if (!name)
237         name = strdup(fullname);
238      else
239         name = strdup(name + 1);
240
241      address = strchr(name, '@');
242      if (address) {
243         *address++ = '\0';
244
245         if (asprintf(&tag, "platform-%s_%s", address, name) < 0)
246            tag = NULL;
247      } else {
248         if (asprintf(&tag, "platform-%s", name) < 0)
249            tag = NULL;
250      }
251
252      free(name);
253   }
254   return tag;
255}
256
257static bool drm_device_matches_tag(drmDevicePtr device, const char *prime_tag)
258{
259   char *tag = drm_construct_id_path_tag(device);
260   int ret;
261
262   if (tag == NULL)
263      return false;
264
265   ret = strcmp(tag, prime_tag);
266
267   free(tag);
268   return ret == 0;
269}
270
271static char *drm_get_id_path_tag_for_fd(int fd)
272{
273   drmDevicePtr device;
274   char *tag;
275
276   if (drmGetDevice2(fd, 0, &device) != 0)
277       return NULL;
278
279   tag = drm_construct_id_path_tag(device);
280   drmFreeDevice(&device);
281   return tag;
282}
283
284int loader_get_user_preferred_fd(int default_fd, bool *different_device)
285{
286/* Arbitrary "maximum" value of drm devices. */
287#define MAX_DRM_DEVICES 32
288   const char *dri_prime = getenv("DRI_PRIME");
289   char *default_tag, *prime = NULL;
290   drmDevicePtr devices[MAX_DRM_DEVICES];
291   int i, num_devices, fd;
292   bool found = false;
293
294   if (dri_prime)
295      prime = strdup(dri_prime);
296#ifdef USE_DRICONF
297   else
298      prime = loader_get_dri_config_device_id();
299#endif
300
301   if (prime == NULL) {
302      *different_device = false;
303      return default_fd;
304   }
305
306   default_tag = drm_get_id_path_tag_for_fd(default_fd);
307   if (default_tag == NULL)
308      goto err;
309
310   num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES);
311   if (num_devices < 0)
312      goto err;
313
314   /* two format are supported:
315    * "1": choose any other card than the card used by default.
316    * id_path_tag: (for example "pci-0000_02_00_0") choose the card
317    * with this id_path_tag.
318    */
319   if (!strcmp(prime,"1")) {
320      /* Hmm... detection for 2-7 seems to be broken. Oh well ...
321       * Pick the first render device that is not our own.
322       */
323      for (i = 0; i < num_devices; i++) {
324         if (devices[i]->available_nodes & 1 << DRM_NODE_RENDER &&
325             !drm_device_matches_tag(devices[i], default_tag)) {
326
327            found = true;
328            break;
329         }
330      }
331   } else {
332      for (i = 0; i < num_devices; i++) {
333         if (devices[i]->available_nodes & 1 << DRM_NODE_RENDER &&
334            drm_device_matches_tag(devices[i], prime)) {
335
336            found = true;
337            break;
338         }
339      }
340   }
341
342   if (!found) {
343      drmFreeDevices(devices, num_devices);
344      goto err;
345   }
346
347   fd = loader_open_device(devices[i]->nodes[DRM_NODE_RENDER]);
348   drmFreeDevices(devices, num_devices);
349   if (fd < 0)
350      goto err;
351
352   close(default_fd);
353
354   *different_device = !!strcmp(default_tag, prime);
355
356   free(default_tag);
357   free(prime);
358   return fd;
359
360 err:
361   *different_device = false;
362
363   free(default_tag);
364   free(prime);
365   return default_fd;
366}
367#else
368int
369loader_open_render_node(const char *name)
370{
371   return -1;
372}
373
374int loader_get_user_preferred_fd(int default_fd, bool *different_device)
375{
376   *different_device = false;
377   return default_fd;
378}
379#endif
380
381#if defined(HAVE_LIBDRM)
382
383static int
384drm_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
385{
386   drmDevicePtr device;
387   int ret;
388
389   if (drmGetDevice2(fd, 0, &device) == 0) {
390      if (device->bustype == DRM_BUS_PCI) {
391         *vendor_id = device->deviceinfo.pci->vendor_id;
392         *chip_id = device->deviceinfo.pci->device_id;
393         ret = 1;
394      }
395      else {
396         log_(_LOADER_DEBUG, "MESA-LOADER: device is not located on the PCI bus\n");
397         ret = 0;
398      }
399      drmFreeDevice(&device);
400   }
401   else {
402      log_(_LOADER_WARNING, "MESA-LOADER: failed to retrieve device information\n");
403      ret = 0;
404   }
405
406   return ret;
407}
408#endif
409
410
411int
412loader_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
413{
414#if HAVE_LIBDRM
415   if (drm_get_pci_id_for_fd(fd, vendor_id, chip_id))
416      return 1;
417#endif
418   return 0;
419}
420
421char *
422loader_get_device_name_for_fd(int fd)
423{
424   char *result = NULL;
425
426#if HAVE_LIBDRM
427   result = drmGetDeviceNameFromFd2(fd);
428#endif
429
430   return result;
431}
432
433char *
434loader_get_driver_for_fd(int fd)
435{
436   int vendor_id, chip_id, i, j;
437   char *driver = NULL;
438
439   /* Allow an environment variable to force choosing a different driver
440    * binary.  If that driver binary can't survive on this FD, that's the
441    * user's problem, but this allows vc4 simulator to run on an i965 host,
442    * and may be useful for some touch testing of i915 on an i965 host.
443    */
444   if (geteuid() == getuid()) {
445      driver = getenv("MESA_LOADER_DRIVER_OVERRIDE");
446      if (driver)
447         return strdup(driver);
448   }
449
450#if defined(HAVE_LIBDRM) && defined(USE_DRICONF)
451   driver = loader_get_dri_config_driver(fd);
452   if (driver)
453      return driver;
454#endif
455
456   if (!loader_get_pci_id_for_fd(fd, &vendor_id, &chip_id)) {
457      driver = loader_get_kernel_driver_name(fd);
458      if (driver)
459         log_(_LOADER_INFO, "using driver %s for %d\n", driver, fd);
460      return driver;
461   }
462
463   for (i = 0; driver_map[i].driver; i++) {
464      if (vendor_id != driver_map[i].vendor_id)
465         continue;
466
467      if (driver_map[i].predicate && !driver_map[i].predicate(fd))
468         continue;
469
470      if (driver_map[i].num_chips_ids == -1) {
471         driver = strdup(driver_map[i].driver);
472         goto out;
473      }
474
475      for (j = 0; j < driver_map[i].num_chips_ids; j++)
476         if (driver_map[i].chip_ids[j] == chip_id) {
477            driver = strdup(driver_map[i].driver);
478            goto out;
479         }
480   }
481
482out:
483   log_(driver ? _LOADER_DEBUG : _LOADER_WARNING,
484         "pci id for fd %d: %04x:%04x, driver %s\n",
485         fd, vendor_id, chip_id, driver);
486   return driver;
487}
488
489void
490loader_set_logger(void (*logger)(int level, const char *fmt, ...))
491{
492   log_ = logger;
493}
494
495/* XXX: Local definition to avoid pulling the heavyweight GL/gl.h and
496 * GL/internal/dri_interface.h
497 */
498
499#ifndef __DRI_DRIVER_GET_EXTENSIONS
500#define __DRI_DRIVER_GET_EXTENSIONS "__driDriverGetExtensions"
501#endif
502
503char *
504loader_get_extensions_name(const char *driver_name)
505{
506   char *name = NULL;
507
508   if (asprintf(&name, "%s_%s", __DRI_DRIVER_GET_EXTENSIONS, driver_name) < 0)
509      return NULL;
510
511   const size_t len = strlen(name);
512   for (size_t i = 0; i < len; i++) {
513      if (name[i] == '-')
514         name[i] = '_';
515   }
516
517   return name;
518}
519