loader: Look for any version of currently linked libudev.so
[mesa.git] / src / loader / loader.c
index 52be7061c1961f07f6adfe55d802e6ad5ce5b5bf..8780587a725413bc29ab2d511f8e653e23816a74 100644 (file)
  *    Rob Clark <robclark@freedesktop.org>
  */
 
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 #ifdef HAVE_LIBUDEV
 #include <assert.h>
 #include <dlfcn.h>
+#include <unistd.h>
+#include <stdlib.h>
+#ifdef USE_DRICONF
+#include "xmlconfig.h"
+#include "xmlpool.h"
+#endif
+#endif
+#ifdef HAVE_SYSFS
+#include <sys/types.h>
 #endif
 #include "loader.h"
 
@@ -92,6 +104,22 @@ static void default_logger(int level, const char *fmt, ...)
 
 static void (*log_)(int level, const char *fmt, ...) = default_logger;
 
+int
+loader_open_device(const char *device_name)
+{
+   int fd;
+#ifdef O_CLOEXEC
+   fd = open(device_name, O_RDWR | O_CLOEXEC);
+   if (fd == -1 && errno == EINVAL)
+#endif
+   {
+      fd = open(device_name, O_RDWR);
+      if (fd != -1)
+         fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+   }
+   return fd;
+}
+
 #ifdef HAVE_LIBUDEV
 #include <libudev.h>
 
@@ -100,26 +128,36 @@ static void *udev_handle = NULL;
 static void *
 udev_dlopen_handle(void)
 {
-   if (!udev_handle) {
-      udev_handle = dlopen("libudev.so.1", RTLD_LOCAL | RTLD_LAZY);
-
-      if (!udev_handle) {
-         /* libudev.so.1 changed the return types of the two unref functions
-          * from voids to pointers.  We don't use those return values, and the
-          * only ABI I've heard that cares about this kind of change (calling
-          * a function with a void * return that actually only returns void)
-          * might be ia64.
-          */
-         udev_handle = dlopen("libudev.so.0", RTLD_LOCAL | RTLD_LAZY);
-
-         if (!udev_handle) {
-            log_(_LOADER_WARNING, "Couldn't dlopen libudev.so.1 or "
-                 "libudev.so.0, driver detection may be broken.\n");
-         }
+   char name[80];
+   unsigned flags = RTLD_NOLOAD | RTLD_LOCAL | RTLD_LAZY;
+   int version;
+
+   /* libudev.so.1 changed the return types of the two unref functions
+    * from voids to pointers.  We don't use those return values, and the
+    * only ABI I've heard that cares about this kind of change (calling
+    * a function with a void * return that actually only returns void)
+    * might be ia64.
+    */
+
+   /* First try opening an already linked libudev, then try loading one */
+   do {
+      for (version = 1; version >= 0; version--) {
+         snprintf(name, sizeof(name), "libudev.so.%d", version);
+         udev_handle = dlopen(name, flags);
+         if (udev_handle)
+            return udev_handle;
       }
-   }
 
-   return udev_handle;
+      if ((flags & RTLD_NOLOAD) == 0)
+         break;
+
+      flags &= ~RTLD_NOLOAD;
+   } while (1);
+
+   log_(_LOADER_WARNING,
+        "Couldn't dlopen libudev.so.1 or "
+        "libudev.so.0, driver detection may be broken.\n");
+   return NULL;
 }
 
 static int dlsym_failed = 0;
@@ -195,9 +233,12 @@ libudev_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
    }
 
    pci_id = udev_device_get_property_value(parent, "PCI_ID");
-   if (pci_id == NULL ||
-       sscanf(pci_id, "%x:%x", vendor_id, chip_id) != 2) {
-      log_(_LOADER_WARNING, "MESA-LOADER: malformed or no PCI ID\n");
+   if (pci_id == NULL) {
+      log_(_LOADER_INFO, "MESA-LOADER: no PCI ID\n");
+      *chip_id = -1;
+      goto out;
+   } else if (sscanf(pci_id, "%x:%x", vendor_id, chip_id) != 2) {
+      log_(_LOADER_WARNING, "MESA-LOADER: malformed PCI ID\n");
       *chip_id = -1;
       goto out;
    }
@@ -210,6 +251,254 @@ out:
 
    return (*chip_id >= 0);
 }
+
+static char *
+get_render_node_from_id_path_tag(struct udev *udev,
+                                 char *id_path_tag,
+                                 char another_tag)
+{
+   struct udev_device *device;
+   struct udev_enumerate *e;
+   struct udev_list_entry *entry;
+   const char *path, *id_path_tag_tmp;
+   char *path_res;
+   char found = 0;
+   UDEV_SYMBOL(struct udev_enumerate *, udev_enumerate_new,
+               (struct udev *));
+   UDEV_SYMBOL(int, udev_enumerate_add_match_subsystem,
+               (struct udev_enumerate *, const char *));
+   UDEV_SYMBOL(int, udev_enumerate_add_match_sysname,
+               (struct udev_enumerate *, const char *));
+   UDEV_SYMBOL(int, udev_enumerate_scan_devices,
+               (struct udev_enumerate *));
+   UDEV_SYMBOL(struct udev_list_entry *, udev_enumerate_get_list_entry,
+               (struct udev_enumerate *));
+   UDEV_SYMBOL(struct udev_list_entry *, udev_list_entry_get_next,
+               (struct udev_list_entry *));
+   UDEV_SYMBOL(const char *, udev_list_entry_get_name,
+               (struct udev_list_entry *));
+   UDEV_SYMBOL(struct udev_device *, udev_device_new_from_syspath,
+               (struct udev *, const char *));
+   UDEV_SYMBOL(const char *, udev_device_get_property_value,
+               (struct udev_device *, const char *));
+   UDEV_SYMBOL(const char *, udev_device_get_devnode,
+               (struct udev_device *));
+   UDEV_SYMBOL(struct udev_device *, udev_device_unref,
+               (struct udev_device *));
+
+   e = udev_enumerate_new(udev);
+   udev_enumerate_add_match_subsystem(e, "drm");
+   udev_enumerate_add_match_sysname(e, "render*");
+
+   udev_enumerate_scan_devices(e);
+   udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
+      path = udev_list_entry_get_name(entry);
+      device = udev_device_new_from_syspath(udev, path);
+      if (!device)
+         continue;
+      id_path_tag_tmp = udev_device_get_property_value(device, "ID_PATH_TAG");
+      if (id_path_tag_tmp) {
+         if ((!another_tag && !strcmp(id_path_tag, id_path_tag_tmp)) ||
+             (another_tag && strcmp(id_path_tag, id_path_tag_tmp))) {
+            found = 1;
+            break;
+         }
+      }
+      udev_device_unref(device);
+   }
+
+   if (found) {
+      path_res = strdup(udev_device_get_devnode(device));
+      udev_device_unref(device);
+      return path_res;
+   }
+   return NULL;
+}
+
+static char *
+get_id_path_tag_from_fd(struct udev *udev, int fd)
+{
+   struct udev_device *device;
+   const char *id_path_tag_tmp;
+   char *id_path_tag;
+   UDEV_SYMBOL(const char *, udev_device_get_property_value,
+               (struct udev_device *, const char *));
+   UDEV_SYMBOL(struct udev_device *, udev_device_unref,
+               (struct udev_device *));
+
+   device = udev_device_new_from_fd(udev, fd);
+   if (!device)
+      return NULL;
+
+   id_path_tag_tmp = udev_device_get_property_value(device, "ID_PATH_TAG");
+   if (!id_path_tag_tmp)
+      return NULL;
+
+   id_path_tag = strdup(id_path_tag_tmp);
+
+   udev_device_unref(device);
+   return id_path_tag;
+}
+
+#ifdef USE_DRICONF
+const char __driConfigOptionsLoader[] =
+DRI_CONF_BEGIN
+    DRI_CONF_SECTION_INITIALIZATION
+        DRI_CONF_DEVICE_ID_PATH_TAG()
+    DRI_CONF_SECTION_END
+DRI_CONF_END;
+#endif
+
+int loader_get_user_preferred_fd(int default_fd, int *different_device)
+{
+   struct udev *udev;
+#ifdef USE_DRICONF
+   driOptionCache defaultInitOptions;
+   driOptionCache userInitOptions;
+#endif
+   const char *dri_prime = getenv("DRI_PRIME");
+   char *prime = NULL;
+   int is_different_device = 0, fd = default_fd;
+   char *default_device_id_path_tag;
+   char *device_name = NULL;
+   char another_tag = 0;
+   UDEV_SYMBOL(struct udev *, udev_new, (void));
+   UDEV_SYMBOL(struct udev *, udev_unref, (struct udev *));
+
+   if (dri_prime)
+      prime = strdup(dri_prime);
+#ifdef USE_DRICONF
+   else {
+      driParseOptionInfo(&defaultInitOptions, __driConfigOptionsLoader);
+      driParseConfigFiles(&userInitOptions, &defaultInitOptions, 0, "loader");
+      if (driCheckOption(&userInitOptions, "device_id", DRI_STRING))
+         prime = strdup(driQueryOptionstr(&userInitOptions, "device_id"));
+      driDestroyOptionCache(&userInitOptions);
+      driDestroyOptionInfo(&defaultInitOptions);
+   }
+#endif
+
+   if (prime == NULL) {
+      *different_device = 0;
+      return default_fd;
+   }
+
+   udev = udev_new();
+   if (!udev)
+      goto prime_clean;
+
+   default_device_id_path_tag = get_id_path_tag_from_fd(udev, default_fd);
+   if (!default_device_id_path_tag)
+      goto udev_clean;
+
+   is_different_device = 1;
+   /* two format are supported:
+    * "1": choose any other card than the card used by default.
+    * id_path_tag: (for example "pci-0000_02_00_0") choose the card
+    * with this id_path_tag.
+    */
+   if (!strcmp(prime,"1")) {
+      free(prime);
+      prime = strdup(default_device_id_path_tag);
+      /* request a card with a different card than the default card */
+      another_tag = 1;
+   } else if (!strcmp(default_device_id_path_tag, prime))
+      /* we are to get a new fd (render-node) of the same device */
+      is_different_device = 0;
+
+   device_name = get_render_node_from_id_path_tag(udev,
+                                                  prime,
+                                                  another_tag);
+   if (device_name == NULL) {
+      is_different_device = 0;
+      goto default_device_clean;
+   }
+
+   fd = loader_open_device(device_name);
+   if (fd >= 0) {
+      close(default_fd);
+   } else {
+      fd = default_fd;
+      is_different_device = 0;
+   }
+   free(device_name);
+
+ default_device_clean:
+   free(default_device_id_path_tag);
+ udev_clean:
+   udev_unref(udev);
+ prime_clean:
+   free(prime);
+
+   *different_device = is_different_device;
+   return fd;
+}
+#else
+int loader_get_user_preferred_fd(int default_fd, int *different_device)
+{
+   *different_device = 0;
+   return default_fd;
+}
+#endif
+
+#if defined(HAVE_SYSFS)
+static int
+dev_node_from_fd(int fd, unsigned int *maj, unsigned int *min)
+{
+   struct stat buf;
+
+   if (fstat(fd, &buf) < 0) {
+      log_(_LOADER_WARNING, "MESA-LOADER: failed to stat fd %d\n", fd);
+      return -1;
+   }
+
+   if (!S_ISCHR(buf.st_mode)) {
+      log_(_LOADER_WARNING, "MESA-LOADER: fd %d not a character device\n", fd);
+      return -1;
+   }
+
+   *maj = major(buf.st_rdev);
+   *min = minor(buf.st_rdev);
+
+   return 0;
+}
+
+static int
+sysfs_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
+{
+   unsigned int maj, min;
+   FILE *f;
+   char buf[0x40];
+
+   if (dev_node_from_fd(fd, &maj, &min) < 0) {
+      *chip_id = -1;
+      return 0;
+   }
+
+   snprintf(buf, sizeof(buf), "/sys/dev/char/%d:%d/device/vendor", maj, min);
+   if (!(f = fopen(buf, "r"))) {
+      *chip_id = -1;
+      return 0;
+   }
+   if (fscanf(f, "%x", vendor_id) != 1) {
+      *chip_id = -1;
+      fclose(f);
+      return 0;
+   }
+   fclose(f);
+   snprintf(buf, sizeof(buf), "/sys/dev/char/%d:%d/device/device", maj, min);
+   if (!(f = fopen(buf, "r"))) {
+      *chip_id = -1;
+      return 0;
+   }
+   if (fscanf(f, "%x", chip_id) != 1) {
+      *chip_id = -1;
+      fclose(f);
+      return 0;
+   }
+   fclose(f);
+   return 1;
+}
 #endif
 
 #if !defined(__NOT_HAVE_DRM_H)
@@ -291,6 +580,10 @@ loader_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
    if (libudev_get_pci_id_for_fd(fd, vendor_id, chip_id))
       return 1;
 #endif
+#if HAVE_SYSFS
+   if (sysfs_get_pci_id_for_fd(fd, vendor_id, chip_id))
+      return 1;
+#endif
 #if !defined(__NOT_HAVE_DRM_H)
    if (drm_get_pci_id_for_fd(fd, vendor_id, chip_id))
       return 1;
@@ -314,6 +607,9 @@ libudev_get_device_name_for_fd(int fd)
                (struct udev_device *));
    UDEV_SYMBOL(struct udev *, udev_unref, (struct udev *));
 
+   if (dlsym_failed)
+      return NULL;
+
    udev = udev_new();
    device = udev_device_new_from_fd(udev, fd);
    if (device == NULL)
@@ -332,6 +628,46 @@ out:
 #endif
 
 
+#if HAVE_SYSFS
+static char *
+sysfs_get_device_name_for_fd(int fd)
+{
+   char *device_name = NULL;
+   unsigned int maj, min;
+   FILE *f;
+   char buf[0x40];
+   static const char match[9] = "\0DEVNAME=";
+   int expected = 1;
+
+   if (dev_node_from_fd(fd, &maj, &min) < 0)
+      return NULL;
+
+   snprintf(buf, sizeof(buf), "/sys/dev/char/%d:%d/uevent", maj, min);
+   if (!(f = fopen(buf, "r")))
+       return NULL;
+
+   while (expected < sizeof(match)) {
+      int c = getc(f);
+
+      if (c == EOF) {
+         fclose(f);
+         return NULL;
+      } else if (c == match[expected] )
+         expected++;
+      else
+         expected = 0;
+   }
+
+   strcpy(buf, "/dev/");
+   if (fgets(buf + 5, sizeof(buf) - 5, f))
+      device_name = strdup(buf);
+
+   fclose(f);
+   return device_name;
+}
+#endif
+
+
 char *
 loader_get_device_name_for_fd(int fd)
 {
@@ -340,6 +676,10 @@ loader_get_device_name_for_fd(int fd)
 #if HAVE_LIBUDEV
    if ((result = libudev_get_device_name_for_fd(fd)))
       return result;
+#endif
+#if HAVE_SYSFS
+   if ((result = sysfs_get_device_name_for_fd(fd)))
+      return result;
 #endif
    return result;
 }