* 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/stat.h>
#include <sys/types.h>
#endif
#include "loader.h"
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>
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;
}
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;
}
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)
(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)