Files
mesa/src/loader/loader.c
T
Axel Davy 3ecd9e1a93 loader: Use drirc device_id parameter in complement to DRI_PRIME
DRI_PRIME is not very handy, because you have to launch the executable
with it set, which is not always easy to do.
By using drirc, the user specifies the target executable
and the device to use. After that the program will be launched everytime
on the target device.

For example if .drirc contains:

<driconf>
    <device driver="loader">
        <application name="Glmark2" executable="glmark2">
            <option name="device_id" value="pci-0000_01_00_0" />
        </application>
    </device>
</driconf>

Then glmark2 will use if possible the render-node of
ID_PATH_TAG pci-0000_01_00_0.

v2: Fix compilation issue
v3: Add "-lm" and rebase.

Signed-off-by: Axel Davy <axel.davy@ens.fr>
Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
2014-07-01 13:07:40 +10:00

734 lines
19 KiB
C

/*
* Copyright (C) 2013 Rob Clark <robclark@freedesktop.org>
*
* This code is derived from the following files.
*
* * src/glx/dri3_common.c
* Copyright © 2013 Keith Packard
*
* * src/egl/drivers/dri2/common.c
* * src/gbm/backends/dri/driver_name.c
* Copyright © 2011 Intel Corporation
*
* Authors:
* Kristian Høgsberg <krh@bitplanet.net>
* Benjamin Franzke <benjaminfranzke@googlemail.com>
*
* * src/gallium/targets/egl-static/egl.c
* Copyright (C) 2010-2011 LunarG Inc.
*
* Authors:
* Chia-I Wu <olv@lunarg.com>
*
* * src/gallium/state_trackers/egl/drm/native_drm.c
* Copyright (C) 2010 Chia-I Wu <olv@0xlab.org>
*
* * src/egl/drivers/dri2/platform_android.c
*
* Copyright (C) 2010-2011 Chia-I Wu <olvaffe@gmail.com>
* Copyright (C) 2010-2011 LunarG Inc.
*
* Based on platform_x11, which has
*
* Copyright © 2011 Intel Corporation
*
* * src/gallium/auxiliary/pipe-loader/pipe_loader_drm.c
* Copyright 2011 Intel Corporation
* Copyright 2012 Francisco Jerez
* All Rights Reserved.
*
* Authors:
* Kristian Høgsberg <krh@bitplanet.net>
* Benjamin Franzke <benjaminfranzke@googlemail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Authors:
* Rob Clark <robclark@freedesktop.org>
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_LIBUDEV
#include <assert.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.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"
#ifndef __NOT_HAVE_DRM_H
#include <xf86drm.h>
#endif
#define __IS_LOADER
#include "pci_id_driver_map.h"
static void default_logger(int level, const char *fmt, ...)
{
if (level <= _LOADER_WARNING) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
}
static void (*log_)(int level, const char *fmt, ...) = default_logger;
#ifdef HAVE_LIBUDEV
#include <libudev.h>
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");
}
}
}
return udev_handle;
}
static int dlsym_failed = 0;
static void *
checked_dlsym(void *dlopen_handle, const char *name)
{
void *result = dlsym(dlopen_handle, name);
if (!result)
dlsym_failed = 1;
return result;
}
#define UDEV_SYMBOL(ret, name, args) \
ret (*name) args = checked_dlsym(udev_dlopen_handle(), #name);
static inline struct udev_device *
udev_device_new_from_fd(struct udev *udev, int fd)
{
struct udev_device *device;
struct stat buf;
UDEV_SYMBOL(struct udev_device *, udev_device_new_from_devnum,
(struct udev *udev, char type, dev_t devnum));
if (dlsym_failed)
return NULL;
if (fstat(fd, &buf) < 0) {
log_(_LOADER_WARNING, "MESA-LOADER: failed to stat fd %d\n", fd);
return NULL;
}
device = udev_device_new_from_devnum(udev, 'c', buf.st_rdev);
if (device == NULL) {
log_(_LOADER_WARNING,
"MESA-LOADER: could not create udev device for fd %d\n", fd);
return NULL;
}
return device;
}
static int
libudev_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
{
struct udev *udev = NULL;
struct udev_device *device = NULL, *parent;
const char *pci_id;
UDEV_SYMBOL(struct udev *, udev_new, (void));
UDEV_SYMBOL(struct udev_device *, udev_device_get_parent,
(struct udev_device *));
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 *));
UDEV_SYMBOL(struct udev *, udev_unref, (struct udev *));
*chip_id = -1;
if (dlsym_failed)
return 0;
udev = udev_new();
device = udev_device_new_from_fd(udev, fd);
if (!device)
goto out;
parent = udev_device_get_parent(device);
if (parent == NULL) {
log_(_LOADER_WARNING, "MESA-LOADER: could not get parent device\n");
goto out;
}
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");
*chip_id = -1;
goto out;
}
out:
if (device)
udev_device_unref(device);
if (udev)
udev_unref(udev);
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;
}
static int
drm_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 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 = drm_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)
/* for i915 */
#include <i915_drm.h>
/* for radeon */
#include <radeon_drm.h>
static int
drm_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
{
drmVersionPtr version;
*chip_id = -1;
version = drmGetVersion(fd);
if (!version) {
log_(_LOADER_WARNING, "MESA-LOADER: invalid drm fd\n");
return 0;
}
if (!version->name) {
log_(_LOADER_WARNING, "MESA-LOADER: unable to determine the driver name\n");
drmFreeVersion(version);
return 0;
}
if (strcmp(version->name, "i915") == 0) {
struct drm_i915_getparam gp;
int ret;
*vendor_id = 0x8086;
memset(&gp, 0, sizeof(gp));
gp.param = I915_PARAM_CHIPSET_ID;
gp.value = chip_id;
ret = drmCommandWriteRead(fd, DRM_I915_GETPARAM, &gp, sizeof(gp));
if (ret) {
log_(_LOADER_WARNING, "MESA-LOADER: failed to get param for i915\n");
*chip_id = -1;
}
}
else if (strcmp(version->name, "radeon") == 0) {
struct drm_radeon_info info;
int ret;
*vendor_id = 0x1002;
memset(&info, 0, sizeof(info));
info.request = RADEON_INFO_DEVICE_ID;
info.value = (unsigned long) chip_id;
ret = drmCommandWriteRead(fd, DRM_RADEON_INFO, &info, sizeof(info));
if (ret) {
log_(_LOADER_WARNING, "MESA-LOADER: failed to get info for radeon\n");
*chip_id = -1;
}
}
else if (strcmp(version->name, "nouveau") == 0) {
*vendor_id = 0x10de;
/* not used */
*chip_id = 0;
}
else if (strcmp(version->name, "vmwgfx") == 0) {
*vendor_id = 0x15ad;
/* assume SVGA II */
*chip_id = 0x0405;
}
drmFreeVersion(version);
return (*chip_id >= 0);
}
#endif
int
loader_get_pci_id_for_fd(int fd, int *vendor_id, int *chip_id)
{
#if HAVE_LIBUDEV
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;
#endif
return 0;
}
#ifdef HAVE_LIBUDEV
static char *
libudev_get_device_name_for_fd(int fd)
{
char *device_name = NULL;
struct udev *udev;
struct udev_device *device;
const char *const_device_name;
UDEV_SYMBOL(struct udev *, udev_new, (void));
UDEV_SYMBOL(const char *, udev_device_get_devnode,
(struct udev_device *));
UDEV_SYMBOL(struct udev_device *, udev_device_unref,
(struct udev_device *));
UDEV_SYMBOL(struct udev *, udev_unref, (struct udev *));
udev = udev_new();
device = udev_device_new_from_fd(udev, fd);
if (device == NULL)
return NULL;
const_device_name = udev_device_get_devnode(device);
if (!const_device_name)
goto out;
device_name = strdup(const_device_name);
out:
udev_device_unref(device);
udev_unref(udev);
return device_name;
}
#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)
{
char *result = NULL;
#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;
}
char *
loader_get_driver_for_fd(int fd, unsigned driver_types)
{
int vendor_id, chip_id, i, j;
char *driver = NULL;
if (!driver_types)
driver_types = _LOADER_GALLIUM | _LOADER_DRI;
if (!loader_get_pci_id_for_fd(fd, &vendor_id, &chip_id)) {
#ifndef __NOT_HAVE_DRM_H
/* fallback to drmGetVersion(): */
drmVersionPtr version = drmGetVersion(fd);
if (!version) {
log_(_LOADER_WARNING, "failed to get driver name for fd %d\n", fd);
return NULL;
}
driver = strndup(version->name, version->name_len);
log_(_LOADER_INFO, "using driver %s for %d\n", driver, fd);
drmFreeVersion(version);
#endif
return driver;
}
for (i = 0; driver_map[i].driver; i++) {
if (vendor_id != driver_map[i].vendor_id)
continue;
if (!(driver_types & driver_map[i].driver_types))
continue;
if (driver_map[i].predicate && !driver_map[i].predicate(fd))
continue;
if (driver_map[i].num_chips_ids == -1) {
driver = strdup(driver_map[i].driver);
goto out;
}
for (j = 0; j < driver_map[i].num_chips_ids; j++)
if (driver_map[i].chip_ids[j] == chip_id) {
driver = strdup(driver_map[i].driver);
goto out;
}
}
out:
log_(driver ? _LOADER_DEBUG : _LOADER_WARNING,
"pci id for fd %d: %04x:%04x, driver %s\n",
fd, vendor_id, chip_id, driver);
return driver;
}
void
loader_set_logger(void (*logger)(int level, const char *fmt, ...))
{
log_ = logger;
}