/**
* \file xf86drm.c
* User-level interface to DRM device
*
* \author Rickard E. (Rik) Faith <faith@valinux.com>
* \author Kevin E. Martin <martin@valinux.com>
*/
/*
* Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
* Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
* All Rights Reserved.
*
* 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
* PRECISION INSIGHT AND/OR ITS SUPPLIERS 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.
*/
#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif
#ifdef XFree86Server
# include "xf86.h"
# include "xf86_OSproc.h"
# include "drm.h"
# include "xf86_ansic.h"
# define _DRM_MALLOC xalloc
# define _DRM_FREE xfree
# ifndef XFree86LOADER
# include <sys/mman.h>
# endif
#else
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <ctype.h>
# include <fcntl.h>
# include <errno.h>
# include <signal.h>
# include <sys/types.h>
# include <sys/stat.h>
# define stat_t struct stat
# include <sys/ioctl.h>
# include <sys/mman.h>
# include <sys/time.h>
# include <stdarg.h>
# define _DRM_MALLOC malloc
# define _DRM_FREE free
# include "drm.h"
#endif
/* Not all systems have MAP_FAILED defined */
#ifndef MAP_FAILED
#define MAP_FAILED ((void *)-1)
#endif
#include "xf86drm.h"
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
#define DRM_MAJOR 145
#endif
#ifdef __NetBSD__
#define DRM_MAJOR 34
#endif
# ifdef __OpenBSD__
# define DRM_MAJOR 81
# endif
#ifndef DRM_MAJOR
#define DRM_MAJOR 226 /* Linux */
#endif
#ifndef DRM_MAX_MINOR
#define DRM_MAX_MINOR 16
#endif
/*
* This definition needs to be changed on some systems if dev_t is a structure.
* If there is a header file we can get it from, there would be best.
*/
#ifndef makedev
#define makedev(x,y) ((dev_t)(((x) << 8) | (y)))
#endif
#define DRM_MSG_VERBOSITY 3
/**
* Output a message to stderr.
*
* \param format printf() like format string.
*
* \internal
* This function is a wrapper around vfprintf().
*/
static void
drmMsg(const char *format, ...)
{
va_list ap;
#ifndef XFree86Server
const char *env;
if ((env = getenv("LIBGL_DEBUG")) && strstr(env, "verbose"))
#endif
{
va_start(ap, format);
#ifdef XFree86Server
xf86VDrvMsgVerb(-1, X_NONE, DRM_MSG_VERBOSITY, format, ap);
#else
vfprintf(stderr, format, ap);
#endif
va_end(ap);
}
}
static void *drmHashTable = NULL; /* Context switch callbacks */
typedef struct drmHashEntry {
int fd;
void (*f)(int, void *, void *);
void *tagTable;
} drmHashEntry;
void *drmMalloc(int size)
{
void *pt;
if ((pt = _DRM_MALLOC(size))) memset(pt, 0, size);
return pt;
}
void drmFree(void *pt)
{
if (pt) _DRM_FREE(pt);
}
/* drmStrdup can't use strdup(3), since it doesn't call _DRM_MALLOC... */
static char *drmStrdup(const char *s)
{
char *retval;
if (!s)
return NULL;
retval = _DRM_MALLOC(strlen(s)+1);
if (!retval)
return NULL;
strcpy(retval, s);
return retval;
}
static unsigned long drmGetKeyFromFd(int fd)
{
stat_t st;
st.st_rdev = 0;
fstat(fd, &st);
return st.st_rdev;
}
static drmHashEntry *drmGetEntry(int fd)
{
unsigned long key = drmGetKeyFromFd(fd);
void *value;
drmHashEntry *entry;
if (!drmHashTable) drmHashTable = drmHashCreate();
if (drmHashLookup(drmHashTable, key, &value)) {
entry = drmMalloc(sizeof(*entry));
entry->fd = fd;
entry->f = NULL;
entry->tagTable = drmHashCreate();
drmHashInsert(drmHashTable, key, entry);
} else {
entry = value;
}
return entry;
}
/**
* Compare two busid strings
*
* \param first
* \param second
*
* \return 1 if matched.
*
* \internal
* This function compares two bus ID strings. It understands the older
* PCI:b:d:f format and the newer pci:oooo:bb:dd.f format. In the format, o is
* domain, b is bus, d is device, f is function.
*/
static int drmMatchBusID(const char *id1, const char *id2)
{
/* First, check if the IDs are exactly the same */
if (strcasecmp(id1, id2) == 0)
return 1;
/* Try to match old/new-style PCI bus IDs. */
if (strncasecmp(id1, "pci", 3) == 0) {
int o1, b1, d1, f1;
int o2, b2, d2, f2;
int ret;
ret = sscanf(id1, "pci:%04x:%02x:%02x.%d", &o1, &b1, &d1, &f1);
if (ret != 4) {
o1 = 0;
ret = sscanf(id1, "PCI:%d:%d:%d", &b1, &d1, &f1);
if (ret != 3)
return 0;
}
ret = sscanf(id2, "pci:%04x:%02x:%02x.%d", &o2, &b2, &d2, &f2);
if (ret != 4) {
o2 = 0;
ret = sscanf(id2, "PCI:%d:%d:%d", &b2, &d2, &f2);
if (ret != 3)
return 0;
}
if ((o1 != o2) || (b1 != b2) || (d1 != d2) || (f1 != f2))
return 0;
else
return 1;
}
return 0;
}
/**
* Open the DRM device, creating it if necessary.
*
* \param dev major and minor numbers of the device.
* \param minor minor number of the device.
*
* \return a file descriptor on success, or a negative value on error.
*
* \internal
* Assembles the device name from \p minor and opens it, creating the device
* special file node with the major and minor numbers specified by \p dev and
* parent directory if necessary and was called by root.
*/
static int drmOpenDevice(long dev, int minor)
{
stat_t st;
char buf[64];
int fd;
mode_t devmode = DRM_DEV_MODE;
int isroot = !geteuid();
#if defined(XFree86Server)
uid_t user = DRM_DEV_UID;
gid_t group = DRM_DEV_GID;
#endif
sprintf(buf, DRM_DEV_NAME, DRM_DIR_NAME, minor);
drmMsg("drmOpenDevice: node name is %s\n", buf);
#if defined(XFree86Server)
devmode = xf86ConfigDRI.mode ? xf86ConfigDRI.mode : DRM_DEV_MODE;
devmode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
group = (xf86ConfigDRI.group >= 0) ? xf86ConfigDRI.group : DRM_DEV_GID;
#endif
if (stat(DRM_DIR_NAME, &st)) {
if (!isroot) return DRM_ERR_NOT_ROOT;
mkdir(DRM_DIR_NAME, DRM_DEV_DIRMODE);
chown(DRM_DIR_NAME, 0, 0); /* root:root */
chmod(DRM_DIR_NAME, DRM_DEV_DIRMODE);
}
/* Check if the device node exists and create it if necessary. */
if (stat(buf, &st)) {
if (!isroot) return DRM_ERR_NOT_ROOT;
remove(buf);
mknod(buf, S_IFCHR | devmode, dev);
}
#if defined(XFree86Server)
chown(buf, user, group);
chmod(buf, devmode);
#endif
fd = open(buf, O_RDWR, 0);
drmMsg("drmOpenDevice: open result is %d, (%s)\n",
fd, fd < 0 ? strerror(errno) : "OK");
if (fd >= 0) return fd;
/* Check if the device node is not what we expect it to be, and recreate it
* and try again if so.
*/
if (st.st_rdev != dev) {
if (!isroot) return DRM_ERR_NOT_ROOT;
remove(buf);
mknod(buf, S_IFCHR | devmode, dev);
#if defined(XFree86Server)
chown(buf, user, group);
chmod(buf, devmode);
#endif
}
fd = open(buf, O_RDWR, 0);
drmMsg("drmOpenDevice: open result is %d, (%s)\n",
fd, fd < 0 ? strerror(errno) : "OK");
if (fd >= 0) return fd;
drmMsg("drmOpenDevice: Open failed\n");
remove(buf);
return -errno;
}
/**
* Open the DRM device
*
* \param minor device minor number.
* \param create allow to create the device if set.
*
* \return a file descriptor on success, or a negative value on error.
*
* \internal
* Calls drmOpenDevice() if \p create is set, otherwise assembles the device
* name from \p minor and opens it.
*/
static int drmOpenMinor(int minor, int create)
{
int fd;
char buf[64];
if (create) return drmOpenDevice(makedev(DRM_MAJOR, minor), minor);
sprintf(buf, DRM_DEV_NAME, DRM_DIR_NAME, minor);
if ((fd = open(buf, O_RDWR, 0)) >= 0) return fd;
return -errno;
}
/**
* Determine whether the DRM kernel driver has been loaded.
*
* \return 1 if the DRM driver is loaded, 0 otherwise.
*
* \internal
* Determine the presence of the kernel driver by attempting to open the 0
* minor and get version information. For backward compatibility with older
* Linux implementations, /proc/dri is also checked.
*/
int drmAvailable(void)
{
drmVersionPtr version;
int retval = 0;
int fd;
if ((fd = drmOpenMinor(0, 1)) < 0) {
#ifdef __linux__
/* Try proc for backward Linux compatibility */
if (!access("/proc/dri/0", R_OK)) return 1;
#endif
return 0;
}
if ((version = drmGetVersion(fd))) {
retval = 1;
drmFreeVersion(version);
}
close(fd);
return retval;
}
/**
* Open the device by bus ID.
*
* \param busid bus ID.
*
* \return a file descriptor on success, or a negative value on error.
*
* \internal
* This function attempts to open every possible minor (up to DRM_MAX_MINOR),
* comparing the device bus ID with the one supplied.
*
* \sa drmOpenMinor() and drmGetBusid().
*/
static int drmOpenByBusid(const char *busid)
{
int i;
int fd;
const char *buf;
drmSetVersion sv;
drmMsg("drmOpenByBusid: Searching for BusID %s\n", busid);
for (i = 0; i < DRM_MAX_MINOR; i++) {
fd = drmOpenMinor(i, 1);
drmMsg("drmOpenByBusid: drmOpenMinor returns %d\n", fd);
if (fd >= 0) {
sv.drm_di_major = 1;
sv.drm_di_minor = 1;
sv.drm_dd_major = -1; /* Don't care */
sv.drm_dd_minor = -1; /* Don't care */
drmSetInterfaceVersion(fd, &sv);
buf = drmGetBusid(fd);
drmMsg("drmOpenByBusid: drmGetBusid reports %s\n", buf);
if (buf && drmMatchBusID(buf, busid)) {
drmFreeBusid(buf);
return fd;
}
if (buf) drmFreeBusid(buf);
close(fd);
}
}
return -1;
}
/**
* Open the device by name.
*
* \param name driver name.
*
* \return a file descriptor on success, or a negative value on error.
*
* \internal
* This function opens the first minor number that matches the driver name and
* isn't already in use. If it's in use it then it will already have a bus ID
* assigned.
*
* \sa drmOpenMinor(), drmGetVersion() and drmGetBusid().
*/
static int drmOpenByName(const char *name)
{
int i;
int fd;
drmVersionPtr version;
char * id;
if (!drmAvailable()) {
#if !defined(XFree86Server)
return -1;
#else
/* try to load the kernel module now */
if (!xf86LoadKernelModule(name)) {
ErrorF("[drm] failed to load kernel module \"%s\"\n",
name);
return -1;
}
#endif
}
/*
* Open the first minor number that matches the driver name and isn't
* already in use. If it's in use it will have a busid assigned already.
*/
for (i = 0; i < DRM_MAX_MINOR; i++) {
if ((fd = drmOpenMinor(i, 1)) >= 0) {
if ((version = drmGetVersion(fd))) {
if (!strcmp(version->name, name)) {
drmFreeVersion(version);
id = drmGetBusid(fd);
drmMsg("drmGetBusid returned '%s'\n", id ? id : "NULL");
if (!id || !*id) {
if (id)
drmFreeBusid(id);
return fd;
} else {
drmFreeBusid(id);
}
} else {
drmFreeVersion(version);
}
}
close(fd);
}
}
#ifdef __linux__
/* Backward-compatibility /proc support */
for (i = 0; i < 8; i++) {
char proc_name[64], buf[512];
char *driver, *pt, *devstring;
int retcode;
sprintf(proc_name, "/proc/dri/%d/name", i);
if ((fd = open(proc_name, 0, 0)) >= 0) {
retcode = read(fd, buf, sizeof(buf)-1);
close(fd);
if (retcode) {
buf[retcode-1] = '\0';
for (driver = pt = buf; *pt && *pt != ' '; ++pt)
;
if (*pt) { /* Device is next */
*pt = '\0';
if (!strcmp(driver, name)) { /* Match */
for (devstring = ++pt; *pt && *pt != ' '; ++pt)
;
if (*pt) { /* Found busid */
return drmOpenByBusid(++pt);
} else { /* No busid */
return drmOpenDevice(strtol(devstring, NULL, 0),i);
}
}
}
}
}
}
#endif
return -1;
}
/**
* Open the DRM device.
*
* Looks up the specified name and bus ID, and opens the device found. The
* entry in /dev/dri is created if necessary and if called by root.
*
* \param name driver name. Not referenced if bus ID is supplied.
* \param busid bus ID. Zero if not known.
*
* \return a file descriptor on success, or a negative value on error.
*
* \internal
* It calls drmOpenByBusid() if \p busid is specified or drmOpenByName()
* otherwise.
*/
int drmOpen(const char *name, const char *busid)
{
#ifdef XFree86Server
if (!drmAvailable() && name != NULL) {
/* try to load the kernel */
if (!xf86LoadKernelModule(name)) {
ErrorF("[drm] failed to load kernel module \"%s\"\n",
name);
return -1;
}
}
#endif
if (busid) {
int fd;
fd = drmOpenByBusid(busid);
if (fd >= 0)
return fd;
}
if (name)
return drmOpenByName(name);
return -1;
}
/**
* Free the version information returned by drmGetVersion().
*
* \param v pointer to the version information.
*
* \internal
* It frees the memory pointed by \p %v as well as all the non-null strings
* pointers in it.
*/
void drmFreeVersion(drmVersionPtr v)
{
if (!v) return;
if (v->name) drmFree(v->name);
if (v->date) drmFree(v->date);
if (v->desc) drmFree(v->desc);
drmFree(v);
}
/**
* Free the non-public version information returned by the kernel.
*
* \param v pointer to the version information.
*
* \internal
* Used by drmGetVersion() to free the memory pointed by \p %v as well as all
* the non-null strings pointers in it.
*/
static void drmFreeKernelVersion(drm_version_t *v)
|