/** * \file xf86drm.c * User-level interface to DRM device * * \author Rickard E. (Rik) Faith * \author Kevin E. Martin */ /* * 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_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define stat_t struct stat #include #include #include #include /* 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__) || defined(__DragonFly__) #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 /* * 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 #define DRM_NODE_CONTROL 0 #define DRM_NODE_RENDER 1 static drmServerInfoPtr drm_server_info; void drmSetServerInfo(drmServerInfoPtr info) { drm_server_info = info; } /** * Output a message to stderr. * * \param format printf() like format string. * * \internal * This function is a wrapper around vfprintf(). */ static int drmDebugPrint(const char *format, va_list ap) { return vfprintf(stderr, format, ap); } static int (*drm_debug_print)(const char *format, va_list ap) = drmDebugPrint; void drmMsg(const char *format, ...) { va_list ap; const char *env; if (((env = getenv("LIBGL_DEBUG")) && strstr(env, "verbose")) || drm_server_info) { va_start(ap, format); if (drm_server_info) { drm_server_info->debug_print(format,ap); } else { drm_debug_print(format, ap); } va_end(ap); } } void drmSetDebugMsgFunction(int (*debug_msg_ptr)(const char *format, va_list ap)) { drm_debug_print = debug_msg_ptr; } static void *drmHashTable = NULL; /* Context switch callbacks */ void *drmGetHashTable(void) { return drmHashTable; } void *drmMalloc(int size) { void *pt; if ((pt = malloc(size))) memset(pt, 0, size); return pt; } void drmFree(void *pt) { if (pt) free(pt); } /** * Call ioctl, restarting if it is interupted */ int drmIoctl(int fd, unsigned long request, void *arg) { int ret; do { ret = ioctl(fd, request, arg); } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); return ret; } static unsigned long drmGetKeyFromFd(int fd) { stat_t st; st.st_rdev = 0; fstat(fd, &st); return st.st_rdev; } 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, int pci_domain_ok) { /* 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) { unsigned int o1, b1, d1, f1; unsigned int o2, b2, d2, f2; int ret; ret = sscanf(id1, "pci:%04x:%02x:%02x.%u", &o1, &b1, &d1, &f1); if (ret != 4) { o1 = 0; ret = sscanf(id1, "PCI:%u:%u:%u", &b1, &d1, &f1); if (ret != 3) return 0; } ret = sscanf(id2, "pci:%04x:%02x:%02x.%u", &o2, &b2, &d2, &f2); if (ret != 4) { o2 = 0; ret = sscanf(id2, "PCI:%u:%u:%u", &b2, &d2, &f2); if (ret != 3) return 0; } /* If domains aren't properly supported by the kernel interface, * just ignore them, which sucks less than picking a totally random * card with "open by name" */ if (!pci_domain_ok) o1 = o2 = 0; if ((o1 != o2) || (b1 != b2) || (d1 != d2) || (f1 != f2)) return 0; else return 1; } return 0; } /** * Handles error checking for chown call. * * \param path to file. * \param id of the new owner. * \param id of the new group. * * \return zero if success or -1 if failure. * * \internal * Checks for failure. If failure was caused by signal call chown again. * If any other failure happened then it will output error mesage using * drmMsg() call. */ static int chown_check_return(const char *path, uid_t owner, gid_t group) { int rv; do { rv = chown(path, owner, group); } while (rv != 0 && errno == EINTR); if (rv == 0) return 0; drmMsg("Failed to change owner or group for file %s! %d: %s\n", path, errno, strerror(errno)); return -1; } /** * 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, int type) { stat_t st; char buf[64]; int fd; mode_t devmode = DRM_DEV_MODE, serv_mode; int isroot = !geteuid(); uid_t user = DRM_DEV_UID; gid_t group = DRM_DEV_GID, serv_group; sprintf(buf, type ? DRM_DEV_NAME : DRM_CONTROL_DEV_NAME, DRM_DIR_NAME, minor); drmMsg("drmOpenDevice: node name is %s\n", buf); if (drm_server_info) { drm_server_info->get_perms(&serv_group, &serv_mode); devmode = serv_mode ? serv_mode : DRM_DEV_MODE; devmode &= ~(S_IXUSR|S_IXGRP|S_IXOTH); group = (serv_group >= 0) ? serv_group : DRM_DEV_GID; } #if !defined(UDEV) if (stat(DRM_DIR_NAME, &st)) { if (!isroot) return DRM_ERR_NOT_ROOT; mkdir(DRM_DIR_NAME, DRM_DEV_DIRMODE); chown_check_return(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 (drm_server_info) { chown_check_return(buf, user, group); chmod(buf, devmode); } #else /* if we modprobed then wait for udev */ { int udev_count = 0; wait_for_udev: if (stat(DRM_DIR_NAME, &st)) { usleep(20); udev_count++; if (udev_count == 50) return -1; goto wait_for_udev; } if (stat(buf, &st)) { usleep(20); udev_count++; if (udev_count == 50) return -1; goto wait_for_udev; } } #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; #if !defined(UDEV) /* 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 (drm_server_info) { chown_check_return(buf, user, group); chmod(buf, devmode); } } 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); #endif 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 type) { int fd; char buf[64]; if (create) return drmOpenDevice(makedev(DRM_MAJOR, minor), minor, type); sprintf(buf, type ? DRM_DEV_NAME : DRM_CONTROL_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, DRM_NODE_RENDER)) < 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, pci_domain_ok = 1; 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, DRM_NODE_RENDER); drmMsg("drmOpenByBusid: drmOpenMinor returns %d\n", fd); if (fd >= 0) { /* We need to try for 1.4 first for proper PCI domain support * and if that fails, we know the kernel is busted */ sv.drm_di_major = 1; sv.drm_di_minor = 4; sv.drm_dd_major = -1; /* Don't care */ sv.drm_dd_minor = -1; /* Don't care */ if (drmSetInterfaceVersion(fd, &sv)) { #ifndef __alpha__ pci_domain_ok = 0; #endif 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 */ drmMsg("drmOpenByBusid: Interface 1.4 failed, trying 1.1\n",fd); drmSetInterfaceVersion(fd, &sv); } buf = drmGetBusid(fd); drmMsg("drmOpenByBusid: drmGetBusid reports %s\n", buf); if (buf && drmMatchBusID(buf, busid, pci_domain_ok)) { 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 (!drm_server_info) { return -1; } else { /* try to load the kernel module now */ if (!drm_server_info->load_module(name)) { drmMsg("[drm] failed to load kernel module \"%s\"\n", name); return -1; } } } /* * 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, DRM_NODE_RENDER)) >= 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, DRM_NODE_RENDER); } } } } } } #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) { if (!drmAvailable() && name != NULL && drm_server_info) { /* try to load the kernel */ if (!drm_server_info->load_module(name)) { drmMsg("[drm] failed to load kernel module \"%s\"\n", name); return -1; } } if (busid) { int fd = drmOpenByBusid(busid); if (fd >= 0) return fd; } if (name) return drmOpenByName(name); return -1; } int drmOpenControl(int minor) { return drmOpenMinor(minor, 0, DRM_NODE_CONTROL); } /** * 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; drmFree(v->name); drmFree(v->date); 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) { if (!v) return; drmFree(v->name); drmFree(v->date); drmFree(v->desc); drmFree(v); } /** * Copy version information. * * \param d destination pointer. * \param s source pointer. * * \internal * Used by drmGetVersion() to translate the information returned by the ioctl * interface in a private structure into the public structure counterpart. */ static void drmCopyVersion(drmVersionPtr d, const drm_version_t *s) { d->version_major = s->version_major; d->version_minor = s->version_minor; d->version_patchlevel = s->version_patchlevel; d->name_len = s->name_len; d->name = strdup(s->name); d->date_len = s->date_len; d->date = strdup(s->date); d->desc_len = s->desc_len; d->desc = strdup(s->desc); } /** * Query the driver version information. * * \param fd file descriptor. * * \return pointer to a drmVersion structure which should be freed with * drmFreeVersion(). * * \note Similar information is available via /proc/dri. * * \internal * It gets the version information via successive DRM_IOCTL_VERSION ioctls, * first with zeros to get the string lengths, and then the actually strings. * It also null-terminates them since they might not be already. */ drmVersionPtr drmGetVersion(int fd) { drmVersionPtr retval; drm_version_t *version = drmMalloc(sizeof(*version)); version->name_len = 0; version->name = NULL; version->date_len = 0; version->date = NULL; version->desc_len = 0; version->desc = NULL; if (drmIoctl(fd, DRM_IOCTL_VERSION, version)) { drmFreeKernelVersion(version); return NULL; } if (version->name_len) version->name = drmMalloc(version->name_len + 1); if (version->date_len) version->date = drmMalloc(version->date_len + 1); if (version->desc_len) version->desc = drmMalloc(version->desc_len + 1); if (drmIoctl(fd, DRM_IOCTL_VERSION, version)) { drmMsg("DRM_IOCTL_VERSION: %s\n", strerror(errno)); drmFreeKernelVersion(version); return NULL; } /* The results might not be null-terminated strings, so terminate them. */ if (version->name_len) version->name[version->name_len] = '\0'; if (version->date_len) version->date[version->date_len] = '\0'; if (version->desc_len) version->desc[version->desc_len] = '\0'; retval = drmMalloc(sizeof(*retval)); drmCopyVersion(retval, version); drmFreeKernelVersion(version); return retval; } /** * Get version information for the DRM user space library. * * This version number is driver independent. * * \param fd file descriptor. * * \return version information. * * \internal * This function allocates and fills a drm_version structure with a hard coded * version number. */ drmVersionPtr drmGetLibVersion(int fd) { drm_version_t *version = drmMalloc(sizeof(*version)); /* Version history: * NOTE THIS MUST NOT GO ABOVE VERSION 1.X due to drivers needing it * revision 1.0.x = original DRM interface with no drmGetLibVersion * entry point and many drm extensions * revision 1.1.x = added drmCommand entry points for device extensions * added drmGetLibVersion to identify libdrm.a version * revision 1.2.x = added drmSetInterfaceVersion * modified drmOpen to handle both busid and name * revision 1.3.x = added server + memory manager */ version->version_major = 1; version->version_minor = 3; version->version_patchlevel = 0; return (drmVersionPtr)version; } int drmGetCap(int fd, uint64_t capability, uint64_t *value) { struct drm_get_cap cap = { capability, 0 }; int ret; ret = drmIoctl(fd, DRM_IOCTL_GET_CAP, &cap); if (ret) return ret; *value = cap.value; return 0; } /** * Free the bus ID information. * * \param busid bus ID information string as given by drmGetBusid(). * * \internal * This function is just frees the memory pointed by \p busid. */ void drmFreeBusid(const char *busid) { drmFree((void *)busid); } /** * Get the bus ID of the device. * * \param fd file descriptor. * * \return bus ID string. * * \internal * This function gets the bus ID via successive DRM_IOCTL_GET_UNIQUE ioctls to * get the string length and data, passing the arguments in a drm_unique * structure. */ char *drmGetBusid(int fd) { drm_unique_t u; u.unique_len = 0; u.unique = NULL; if (drmIoctl(fd, DRM_IOCTL_GET_UNIQUE, &u)) return NULL; u.unique = drmMalloc(u.unique_len + 1); if (drmIoctl(fd, DRM_IOCTL_GET_UNIQUE, &u)) return NULL; u.unique[u.unique_len] = '\0'; return u.unique; } /** * Set the bus ID of the device. * * \param fd file descriptor. * \param busid bus ID string. * * \return zero on success, negative on failure. * * \internal * This function is a wrapper around the DRM_IOCTL_SET_UNIQUE ioctl, passing * the arguments in a drm_unique structure. */ int drmSetBusid(int fd, const char *busid) { drm_unique_t u; u.unique = (char *)busid; u.unique_len = strlen(busid); if (drmIoctl(fd, DRM_IOCTL_SET_UNIQUE, &u)) { return -errno; } return 0; } int drmGetMagic(int fd, drm_magic_t * magic) { drm_auth_t auth; *magic = 0; if (drmIoctl(fd, DRM_IOCTL_GET_MAGIC, &auth)) return -errno; *magic = auth.magic; return 0; } int drmAuthMagic(int fd, drm_magic_t magic) { drm_auth_t auth; auth.magic = magic; if (drmIoctl(fd, DRM_IOCTL_AUTH_MAGIC, &auth)) return -errno; return 0; } /** * Specifies a range of memory that is available for mapping by a * non-root process. * * \param fd file descriptor. * \param offset usually the physical address. The actual meaning depends of * the \p type parameter. See below. * \param size of the memory in bytes. * \param type type of the memory to be mapped. * \param flags combination of several flags to modify the function actions. * \param handle will be set to a value that may be used as the offset * parameter for mmap(). * * \return zero on success or a negative value on error. * * \par Mapping the frame buffer * For the frame buffer * - \p offset will be the physical address of the start of the frame buffer, * - \p size will be the size of the frame buffer in bytes, and * - \p type will be DRM_FRAME_BUFFER. * * \par * The area mapped will be uncached. If MTRR support is available in the * kernel, the frame buffer area will be set to write combining. * * \par Mapping the MMIO register area * For the MMIO register area, * - \p offset will be the physical address of the start of the register area, * - \p size will be the size of the register area bytes, and * - \p type will be DRM_REGISTERS. * \par * The area mapped will be uncached. * * \par Mapping the SAREA * For the SAREA, * - \p offset will be ignored and should be set to zero, * - \p size will be the desired size of the SAREA in bytes, * - \p type will be DRM_SHM. * * \par * A shared memory area of the requested size will be created and locked in * kernel memory. This area may be mapped into client-space by using the handle * returned. * * \note May only be called by root. * * \internal * This function is a wrapper around the DRM_IOCTL_ADD_MAP ioctl, passing * the arguments in a drm_map structure. */ int drmAddMap(int fd, drm_handle_t offset, drmSize size, drmMapType type, drmMapFlags flags, drm_handle_t *handle) { drm_map_t map; map.offset = offset; map.size = size; map.handle = 0; map.type = type; map.flags = flags; if (drmIoctl(fd, DRM_IOCTL_ADD_MAP, &map)) return -errno; if (handle) *handle = (drm_handle_t)(uintptr_t)map.handle; return 0; } int drmRmMap(int fd, drm_handle_t handle) { drm_map_t map; map.handle = (void *)(uintptr_t)handle; if(drmIoctl(fd, DRM_IOCTL_RM_MAP, &map)) return -errno; return 0; } /** * Make buffers available for DMA transfers. * * \param fd file descriptor. * \param count number of buffers. * \param size size of each buffer. * \param flags buffer allocation flags. * \param agp_offset offset in the AGP aperture * * \return number of buffers allocated, negative on error. * * \internal * This function is a wrapper around DRM_IOCTL_ADD_BUFS ioctl. * * \sa drm_buf_desc. */ int drmAddBufs(int fd, int count, int size, drmBufDescFlags flags, int agp_offset) { drm_buf_desc_t request; request.count = count; request.size = size; request.low_mark = 0; request.high_mark = 0; request.flags = flags; request.agp_start = agp_offset; if (drmIoctl(fd, DRM_IOCTL_ADD_BUFS, &request)) return -errno; return request.count; } int drmMarkBufs(int fd, double low, double high) { drm_buf_info_t info; int i; info.count = 0; info.list = NULL; if (drmIoctl(fd, DRM_IOCTL_INFO_BUFS, &info)) return -EINVAL; if (!info.count) return -EINVAL; if (!(info.list = drmMalloc(info.count * sizeof(*info.list)))) return -ENOMEM; if (drmIoctl(fd, DRM_IOCTL_INFO_BUFS, &info)) { int retval = -errno; drmFree(info.list); return retval; } for (i = 0; i < info.count; i++) { info.list[i].low_mark = low * info.list[i].count; info.list[i].high_mark = high * info.list[i].count; if (drmIoctl(fd, DRM_IOCTL_MARK_BUFS, &info.list[i])) { int retval = -errno; drmFree(info.list); return retval; } } drmFree(info.list); return 0; } /** * Free buffers. * * \param fd file descriptor. * \param count number of buffers to free. * \param list list of buffers to be freed. * /* * \file xf86drmMode.c * Header for DRM modesetting interface. * * \author Jakob Bornecrantz <wallbraker@gmail.com> * * \par Acknowledgements: * Feb 2007, Dave Airlie <airlied@linux.ie> */ /* * Copyright (c) <year> <copyright holders> * * 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 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. * */ /* * TODO the types we are after are defined in diffrent headers on diffrent * platforms find which headers to include to get uint32_t */ #include <stdint.h> #include <sys/ioctl.h> #include <stdio.h> #include "xf86drmMode.h" #include "xf86drm.h" #include <drm.h> #include <string.h> #include <dirent.h> #include <errno.h> #define U642VOID(x) ((void *)(unsigned long)(x)) #define VOID2U64(x) ((uint64_t)(unsigned long)(x)) /* * Util functions */ void* drmAllocCpy(void *array, int count, int entry_size) { char *r; int i; if (!count || !array || !entry_size) return 0; if (!(r = drmMalloc(count*entry_size))) return 0; for (i = 0; i < count; i++) memcpy(r+(entry_size*i), array+(entry_size*i), entry_size); return r; } /* * A couple of free functions. */ void drmModeFreeModeInfo(struct drm_mode_modeinfo *ptr) { if (!ptr) return; drmFree(ptr); } void drmModeFreeResources(drmModeResPtr ptr) { if (!ptr) return; drmFree(ptr); } void drmModeFreeFB(drmModeFBPtr ptr) { if (!ptr) return; /* we might add more frees later. */ drmFree(ptr); } void drmModeFreeCrtc(drmModeCrtcPtr ptr) { if (!ptr) return; drmFree(ptr); } void drmModeFreeConnector(drmModeConnectorPtr ptr) { if (!ptr) return; drmFree(ptr->modes); drmFree(ptr); } void drmModeFreeEncoder(drmModeEncoderPtr ptr) { drmFree(ptr); } /* * ModeSetting functions. */ drmModeResPtr drmModeGetResources(int fd) { struct drm_mode_card_res res; drmModeResPtr r = 0; memset(&res, 0, sizeof(struct drm_mode_card_res)); if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) return 0; if (res.count_fbs) res.fb_id_ptr = VOID2U64(drmMalloc(res.count_fbs*sizeof(uint32_t))); if (res.count_crtcs) res.crtc_id_ptr = VOID2U64(drmMalloc(res.count_crtcs*sizeof(uint32_t))); if (res.count_connectors) res.connector_id_ptr = VOID2U64(drmMalloc(res.count_connectors*sizeof(uint32_t))); if (res.count_encoders) res.encoder_id_ptr = VOID2U64(drmMalloc(res.count_encoders*sizeof(uint32_t))); if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) { r = NULL; goto err_allocs; } /* * return */ if (!(r = drmMalloc(sizeof(*r)))) return 0; r->min_width = res.min_width; r->max_width = res.max_width; r->min_height = res.min_height; r->max_height = res.max_height; r->count_fbs = res.count_fbs; r->count_crtcs = res.count_crtcs; r->count_connectors = res.count_connectors; r->count_encoders = res.count_encoders; /* TODO we realy should test if these allocs fails. */ r->fbs = drmAllocCpy(U642VOID(res.fb_id_ptr), res.count_fbs, sizeof(uint32_t)); r->crtcs = drmAllocCpy(U642VOID(res.crtc_id_ptr), res.count_crtcs, sizeof(uint32_t)); r->connectors = drmAllocCpy(U642VOID(res.connector_id_ptr), res.count_connectors, sizeof(uint32_t)); r->encoders = drmAllocCpy(U642VOID(res.encoder_id_ptr), res.count_encoders, sizeof(uint32_t)); err_allocs: drmFree(U642VOID(res.fb_id_ptr)); drmFree(U642VOID(res.crtc_id_ptr)); drmFree(U642VOID(res.connector_id_ptr)); drmFree(U642VOID(res.encoder_id_ptr)); return r; } uint32_t drmModeGetHotplug(int fd) { struct drm_mode_hotplug arg; arg.counter = 0; ioctl(fd, DRM_IOCTL_MODE_HOTPLUG, &arg); return arg.counter; } int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id) { struct drm_mode_fb_cmd f; int ret; f.width = width; f.height = height; f.pitch = pitch; f.bpp = bpp; f.depth = depth; f.handle = bo_handle; if ((ret = ioctl(fd, DRM_IOCTL_MODE_ADDFB, &f))) return ret; *buf_id = f.buffer_id; return 0; } int drmModeRmFB(int fd, uint32_t bufferId) { return ioctl(fd, DRM_IOCTL_MODE_RMFB, &bufferId); } drmModeFBPtr drmModeGetFB(int fd, uint32_t buf) { struct drm_mode_fb_cmd info; drmModeFBPtr r; info.buffer_id = buf; if (ioctl(fd, DRM_IOCTL_MODE_GETFB, &info)) return NULL; if (!(r = drmMalloc(sizeof(*r)))) return NULL; r->buffer_id = info.buffer_id; r->width = info.width; r->height = info.height; r->pitch = info.pitch; r->bpp = info.bpp; r->handle = info.handle; r->depth = info.depth; return r; } /* * Crtc functions */ drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) { struct drm_mode_crtc crtc; drmModeCrtcPtr r; crtc.crtc_id = crtcId; if (ioctl(fd, DRM_IOCTL_MODE_GETCRTC, &crtc)) return 0; /* * return */ if (!(r = drmMalloc(sizeof(*r)))) return 0; r->crtc_id = crtc.crtc_id; r->x = crtc.x; r->y = crtc.y; r->mode_valid = crtc.mode_valid; if (r->mode_valid) memcpy(&r->mode, &crtc.mode, sizeof(struct drm_mode_modeinfo)); r->buffer_id = crtc.fb_id; r->gamma_size = crtc.gamma_size; return r; } int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, uint32_t x, uint32_t y, uint32_t *connectors, int count, struct drm_mode_modeinfo *mode) { struct drm_mode_crtc crtc; crtc.x = x; crtc.y = y; crtc.crtc_id = crtcId; crtc.fb_id = bufferId; crtc.set_connectors_ptr = VOID2U64(connectors); crtc.count_connectors = count; if (mode) { memcpy(&crtc.mode, mode, sizeof(struct drm_mode_modeinfo)); crtc.mode_valid = 1; } else crtc.mode_valid = 0; return ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &crtc); } /* * Cursor manipulation */ int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle, uint32_t width, uint32_t height) { struct drm_mode_cursor arg; arg.flags = DRM_MODE_CURSOR_BO; arg.crtc = crtcId; arg.width = width; arg.height = height; arg.handle = bo_handle; return ioctl(fd, DRM_IOCTL_MODE_CURSOR, &arg); } int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) { struct drm_mode_cursor arg; arg.flags = DRM_MODE_CURSOR_MOVE; arg.crtc = crtcId; arg.x = x; arg.y = y; return ioctl(fd, DRM_IOCTL_MODE_CURSOR, &arg); } /* * Encoder get */ drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) { struct drm_mode_get_encoder enc; drmModeEncoderPtr r = NULL; enc.encoder_id = encoder_id; enc.encoder_type = 0; enc.possible_crtcs = 0; enc.possible_clones = 0; if (ioctl(fd, DRM_IOCTL_MODE_GETENCODER, &enc)) return 0; if (!(r = drmMalloc(sizeof(*r)))) return 0; r->encoder_id = enc.encoder_id; r->crtc_id = enc.crtc_id; r->encoder_type = enc.encoder_type; r->possible_crtcs = enc.possible_crtcs; r->possible_clones = enc.possible_clones; return r; } /* * Connector manipulation */ drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id) { struct drm_mode_get_connector conn; drmModeConnectorPtr r = NULL; conn.connector_id = connector_id; conn.connector_type_id = 0; conn.connector_type = 0; conn.count_modes = 0; conn.modes_ptr = 0; conn.count_props = 0; conn.props_ptr = 0; conn.prop_values_ptr = 0; conn.count_encoders = 0; conn.encoders_ptr = 0; if (ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) return 0; if (conn.count_props) { conn.props_ptr = VOID2U64(drmMalloc(conn.count_props*sizeof(uint32_t))); conn.prop_values_ptr = VOID2U64(drmMalloc(conn.count_props*sizeof(uint64_t))); } if (conn.count_modes) conn.modes_ptr = VOID2U64(drmMalloc(conn.count_modes*sizeof(struct drm_mode_modeinfo))); if (conn.count_encoders) conn.encoders_ptr = VOID2U64(drmMalloc(conn.count_encoders*sizeof(uint32_t))); if (ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)) goto err_allocs; if(!(r = drmMalloc(sizeof(*r)))) { goto err_allocs; } r->connector_id = conn.connector_id; r->encoder_id = conn.encoder_id; r->connection = conn.connection; r->mmWidth = conn.mm_width; r->mmHeight = conn.mm_height; r->subpixel = conn.subpixel; r->count_modes = conn.count_modes; /* TODO we should test if these alloc & cpy fails. */ r->count_props = conn.count_props; r->props = drmAllocCpy(U642VOID(conn.props_ptr), conn.count_props, sizeof(uint32_t)); r->prop_values = drmAllocCpy(U642VOID(conn.prop_values_ptr), conn.count_props, sizeof(uint64_t)); r->modes = drmAllocCpy(U642VOID(conn.modes_ptr), conn.count_modes, sizeof(struct drm_mode_modeinfo)); r->count_encoders = conn.count_encoders; r->encoders = drmAllocCpy(U642VOID(conn.encoders_ptr), conn.count_encoders, sizeof(uint32_t)); r->connector_type = conn.connector_type; r->connector_type_id = conn.connector_type_id; if (!r->props || !r->prop_values || !r->modes || !r->encoders) goto err_allocs; err_allocs: drmFree(U642VOID(conn.prop_values_ptr)); drmFree(U642VOID(conn.props_ptr)); drmFree(U642VOID(conn.modes_ptr)); drmFree(U642VOID(conn.encoders_ptr)); return r; } int drmModeAttachMode(int fd, uint32_t connector_id, struct drm_mode_modeinfo *mode_info) { struct drm_mode_mode_cmd res; memcpy(&res.mode, mode_info, sizeof(struct drm_mode_modeinfo)); res.connector_id = connector_id; return ioctl(fd, DRM_IOCTL_MODE_ATTACHMODE, &res); } int drmModeDetachMode(int fd, uint32_t connector_id, struct drm_mode_modeinfo *mode_info) { struct drm_mode_mode_cmd res; memcpy(&res.mode, mode_info, sizeof(struct drm_mode_modeinfo)); res.connector_id = connector_id; return ioctl(fd, DRM_IOCTL_MODE_DETACHMODE, &res); } drmModePropertyPtr drmModeGetProperty(int fd, uint32_t property_id) { struct drm_mode_get_property prop; drmModePropertyPtr r; prop.prop_id = property_id; prop.count_enum_blobs = 0; prop.count_values = 0; prop.flags = 0; prop.enum_blob_ptr = 0; prop.values_ptr = 0; if (ioctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)) return 0; if (prop.count_values) prop.values_ptr = VOID2U64(drmMalloc(prop.count_values * sizeof(uint64_t))); if (prop.count_enum_blobs && (prop.flags & DRM_MODE_PROP_ENUM)) prop.enum_blob_ptr = VOID2U64(drmMalloc(prop.count_enum_blobs * sizeof(struct drm_mode_property_enum))); if (prop.count_enum_blobs && (prop.flags & DRM_MODE_PROP_BLOB)) { prop.values_ptr = VOID2U64(drmMalloc(prop.count_enum_blobs * sizeof(uint32_t))); prop.enum_blob_ptr = VOID2U64(drmMalloc(prop.count_enum_blobs * sizeof(uint32_t))); } if (ioctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)) { r = NULL; goto err_allocs; } if (!(r = drmMalloc(sizeof(*r)))) return NULL; r->prop_id = prop.prop_id; r->count_values = prop.count_values; r->flags = prop.flags; if (prop.count_values) r->values = drmAllocCpy(U642VOID(prop.values_ptr), prop.count_values, sizeof(uint64_t)); if (prop.flags & DRM_MODE_PROP_ENUM) { r->count_enums = prop.count_enum_blobs; r->enums = drmAllocCpy(U642VOID(prop.enum_blob_ptr), prop.count_enum_blobs, sizeof(struct drm_mode_property_enum)); } else if (prop.flags & DRM_MODE_PROP_BLOB) { r->values = drmAllocCpy(U642VOID(prop.values_ptr), prop.count_enum_blobs, sizeof(uint32_t)); r->blob_ids = drmAllocCpy(U642VOID(prop.enum_blob_ptr), prop.count_enum_blobs, sizeof(uint32_t)); r->count_blobs = prop.count_enum_blobs; } strncpy(r->name, prop.name, DRM_PROP_NAME_LEN); r->name[DRM_PROP_NAME_LEN-1] = 0; err_allocs: drmFree(U642VOID(prop.values_ptr)); drmFree(U642VOID(prop.enum_blob_ptr)); return r; } void drmModeFreeProperty(drmModePropertyPtr ptr) { if (!ptr) return; drmFree(ptr->values); drmFree(ptr->enums); drmFree(ptr); } drmModePropertyBlobPtr drmModeGetPropertyBlob(int fd, uint32_t blob_id) { struct drm_mode_get_blob blob; drmModePropertyBlobPtr r; blob.length = 0; blob.data = 0; blob.blob_id = blob_id; if (ioctl(fd, DRM_IOCTL_MODE_GETPROPBLOB, &blob)) return NULL; if (blob.length) blob.data = VOID2U64(drmMalloc(blob.length)); if (ioctl(fd, DRM_IOCTL_MODE_GETPROPBLOB, &blob)) { r = NULL; goto err_allocs; } if (!(r = drmMalloc(sizeof(*r)))) return NULL; r->id = blob.blob_id; r->length = blob.length; r->data = drmAllocCpy(U642VOID(blob.data), 1, blob.length); err_allocs: drmFree(U642VOID(blob.data)); return r; } void drmModeFreePropertyBlob(drmModePropertyBlobPtr ptr) { if (!ptr) return; drmFree(ptr->data); drmFree(ptr); } int drmModeConnectorSetProperty(int fd, uint32_t connector_id, uint32_t property_id, uint64_t value) { struct drm_mode_connector_set_property osp; int ret; osp.connector_id = connector_id; osp.prop_id = property_id; osp.value = value; if ((ret = ioctl(fd, DRM_IOCTL_MODE_SETPROPERTY, &osp))) return ret; return 0; } /* * checks if a modesetting capable driver has attached to the pci id * returns 0 if modesetting supported. * -EINVAL or invalid bus id * -ENOSYS if no modesetting support */ int drmCheckModesettingSupported(const char *busid) { #ifdef __linux__ char pci_dev_dir[1024]; int domain, bus, dev, func; DIR *sysdir; struct dirent *dent; int found = 0, ret; ret = sscanf(busid, "pci:%04x:%02x:%02x.%d", &domain, &bus, &dev, &func); if (ret != 4) return -EINVAL; sprintf(pci_dev_dir, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/drm", domain, bus, dev, func); sysdir = opendir(pci_dev_dir); if (sysdir) { dent = readdir(sysdir); while (dent) { if (!strncmp(dent->d_name, "controlD", 8)) { found = 1; break; } dent = readdir(sysdir); } closedir(sysdir); if (found) return 0; } sprintf(pci_dev_dir, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/", domain, bus, dev, func); sysdir = opendir(pci_dev_dir); if (!sysdir) return -EINVAL; dent = readdir(sysdir); while (dent) { if (!strncmp(dent->d_name, "drm:controlD", 12)) { found = 1; break; } dent = readdir(sysdir); } closedir(sysdir); if (found) return 0; #endif return -ENOSYS; } int drmModeReplaceFB(int fd, uint32_t buffer_id, uint32_t width, uint32_t height, uint8_t depth, uint8_t bpp, uint32_t pitch, uint32_t bo_handle) { struct drm_mode_fb_cmd f; int ret; f.width = width; f.height = height; f.pitch = pitch; f.bpp = bpp; f.depth = depth; f.handle = bo_handle; f.buffer_id = buffer_id; if ((ret = ioctl(fd, DRM_IOCTL_MODE_REPLACEFB, &f))) return ret; return 0; } int drmModeCrtcGetGamma(int fd, uint32_t crtc_id, uint32_t size, uint16_t *red, uint16_t *green, uint16_t *blue) { int ret; struct drm_mode_crtc_lut l; l.crtc_id = crtc_id; l.gamma_size = size; l.red = VOID2U64(red); l.green = VOID2U64(green); l.blue = VOID2U64(blue); if ((ret = ioctl(fd, DRM_IOCTL_MODE_GETGAMMA, &l))) return ret; return 0; } int drmModeCrtcSetGamma(int fd, uint32_t crtc_id, uint32_t size, uint16_t *red, uint16_t *green, uint16_t *blue) { int ret; struct drm_mode_crtc_lut l; l.crtc_id = crtc_id; l.gamma_size = size; l.red = VOID2U64(red); l.green = VOID2U64(green); l.blue = VOID2U64(blue); if ((ret = ioctl(fd, DRM_IOCTL_MODE_SETGAMMA, &l))) return ret; return 0; }