diff options
Diffstat (limited to '')
-rw-r--r-- | xf86drm.c | 5148 |
1 files changed, 5148 insertions, 0 deletions
diff --git a/xf86drm.c b/xf86drm.c new file mode 100644 index 0000000..0faa597 --- /dev/null +++ b/xf86drm.c @@ -0,0 +1,5148 @@ +/** + * \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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <ctype.h> +#include <dirent.h> +#include <stddef.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#define stat_t struct stat +#include <sys/ioctl.h> +#include <sys/time.h> +#include <stdarg.h> +#ifdef MAJOR_IN_MKDEV +#include <sys/mkdev.h> +#endif +#ifdef MAJOR_IN_SYSMACROS +#include <sys/sysmacros.h> +#endif +#if HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#endif +#include <math.h> +#include <inttypes.h> + +#if defined(__FreeBSD__) +#include <sys/param.h> +#include <sys/pciio.h> +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +/* Not all systems have MAP_FAILED defined */ +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + +#include "xf86drm.h" +#include "libdrm_macros.h" +#include "drm_fourcc.h" + +#include "util_math.h" + +#ifdef __DragonFly__ +#define DRM_MAJOR 145 +#endif + +#ifdef __NetBSD__ +#define DRM_MAJOR 34 +#endif + +#ifdef __OpenBSD__ +#ifdef __i386__ +#define DRM_MAJOR 88 +#else +#define DRM_MAJOR 87 +#endif +#endif /* __OpenBSD__ */ + +#ifndef DRM_MAJOR +#define DRM_MAJOR 226 /* Linux */ +#endif + +#if defined(__OpenBSD__) || defined(__DragonFly__) +struct drm_pciinfo { + uint16_t domain; + uint8_t bus; + uint8_t dev; + uint8_t func; + uint16_t vendor_id; + uint16_t device_id; + uint16_t subvendor_id; + uint16_t subdevice_id; + uint8_t revision_id; +}; + +#define DRM_IOCTL_GET_PCIINFO DRM_IOR(0x15, struct drm_pciinfo) +#endif + +#define DRM_MSG_VERBOSITY 3 + +#define memclear(s) memset(&s, 0, sizeof(s)) + +static drmServerInfoPtr drm_server_info; + +static bool drmNodeIsDRM(int maj, int min); +static char *drmGetMinorNameForFD(int fd, int type); + +#define DRM_MODIFIER(v, f, f_name) \ + .modifier = DRM_FORMAT_MOD_##v ## _ ##f, \ + .modifier_name = #f_name + +#define DRM_MODIFIER_INVALID(v, f_name) \ + .modifier = DRM_FORMAT_MOD_INVALID, .modifier_name = #f_name + +#define DRM_MODIFIER_LINEAR(v, f_name) \ + .modifier = DRM_FORMAT_MOD_LINEAR, .modifier_name = #f_name + +/* Intel is abit special as the format doesn't follow other vendors naming + * scheme */ +#define DRM_MODIFIER_INTEL(f, f_name) \ + .modifier = I915_FORMAT_MOD_##f, .modifier_name = #f_name + +struct drmFormatModifierInfo { + uint64_t modifier; + const char *modifier_name; +}; + +struct drmFormatModifierVendorInfo { + uint8_t vendor; + const char *vendor_name; +}; + +#include "generated_static_table_fourcc.h" + +struct drmVendorInfo { + uint8_t vendor; + char *(*vendor_cb)(uint64_t modifier); +}; + +struct drmFormatVendorModifierInfo { + uint64_t modifier; + const char *modifier_name; +}; + +static char * +drmGetFormatModifierNameFromArm(uint64_t modifier); + +static char * +drmGetFormatModifierNameFromNvidia(uint64_t modifier); + +static char * +drmGetFormatModifierNameFromAmd(uint64_t modifier); + +static char * +drmGetFormatModifierNameFromAmlogic(uint64_t modifier); + +static const struct drmVendorInfo modifier_format_vendor_table[] = { + { DRM_FORMAT_MOD_VENDOR_ARM, drmGetFormatModifierNameFromArm }, + { DRM_FORMAT_MOD_VENDOR_NVIDIA, drmGetFormatModifierNameFromNvidia }, + { DRM_FORMAT_MOD_VENDOR_AMD, drmGetFormatModifierNameFromAmd }, + { DRM_FORMAT_MOD_VENDOR_AMLOGIC, drmGetFormatModifierNameFromAmlogic }, +}; + +#ifndef AFBC_FORMAT_MOD_MODE_VALUE_MASK +#define AFBC_FORMAT_MOD_MODE_VALUE_MASK 0x000fffffffffffffULL +#endif + +static const struct drmFormatVendorModifierInfo arm_mode_value_table[] = { + { AFBC_FORMAT_MOD_YTR, "YTR" }, + { AFBC_FORMAT_MOD_SPLIT, "SPLIT" }, + { AFBC_FORMAT_MOD_SPARSE, "SPARSE" }, + { AFBC_FORMAT_MOD_CBR, "CBR" }, + { AFBC_FORMAT_MOD_TILED, "TILED" }, + { AFBC_FORMAT_MOD_SC, "SC" }, + { AFBC_FORMAT_MOD_DB, "DB" }, + { AFBC_FORMAT_MOD_BCH, "BCH" }, + { AFBC_FORMAT_MOD_USM, "USM" }, +}; + +static bool is_x_t_amd_gfx9_tile(uint64_t tile) +{ + switch (tile) { + case AMD_FMT_MOD_TILE_GFX9_64K_S_X: + case AMD_FMT_MOD_TILE_GFX9_64K_D_X: + case AMD_FMT_MOD_TILE_GFX9_64K_R_X: + return true; + } + + return false; +} + +static bool +drmGetAfbcFormatModifierNameFromArm(uint64_t modifier, FILE *fp) +{ + uint64_t mode_value = modifier & AFBC_FORMAT_MOD_MODE_VALUE_MASK; + uint64_t block_size = mode_value & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK; + + const char *block = NULL; + const char *mode = NULL; + bool did_print_mode = false; + + /* add block, can only have a (single) block */ + switch (block_size) { + case AFBC_FORMAT_MOD_BLOCK_SIZE_16x16: + block = "16x16"; + break; + case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8: + block = "32x8"; + break; + case AFBC_FORMAT_MOD_BLOCK_SIZE_64x4: + block = "64x4"; + break; + case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8_64x4: + block = "32x8_64x4"; + break; + } + + if (!block) { + return false; + } + + fprintf(fp, "BLOCK_SIZE=%s,", block); + + /* add mode */ + for (unsigned int i = 0; i < ARRAY_SIZE(arm_mode_value_table); i++) { + if (arm_mode_value_table[i].modifier & mode_value) { + mode = arm_mode_value_table[i].modifier_name; + if (!did_print_mode) { + fprintf(fp, "MODE=%s", mode); + did_print_mode = true; + } else { + fprintf(fp, "|%s", mode); + } + } + } + + return true; +} + +static bool +drmGetAfrcFormatModifierNameFromArm(uint64_t modifier, FILE *fp) +{ + bool scan_layout; + for (unsigned int i = 0; i < 2; ++i) { + uint64_t coding_unit_block = + (modifier >> (i * 4)) & AFRC_FORMAT_MOD_CU_SIZE_MASK; + const char *coding_unit_size = NULL; + + switch (coding_unit_block) { + case AFRC_FORMAT_MOD_CU_SIZE_16: + coding_unit_size = "CU_16"; + break; + case AFRC_FORMAT_MOD_CU_SIZE_24: + coding_unit_size = "CU_24"; + break; + case AFRC_FORMAT_MOD_CU_SIZE_32: + coding_unit_size = "CU_32"; + break; + } + + if (!coding_unit_size) { + if (i == 0) { + return false; + } + break; + } + + if (i == 0) { + fprintf(fp, "P0=%s,", coding_unit_size); + } else { + fprintf(fp, "P12=%s,", coding_unit_size); + } + } + + scan_layout = + (modifier & AFRC_FORMAT_MOD_LAYOUT_SCAN) == AFRC_FORMAT_MOD_LAYOUT_SCAN; + if (scan_layout) { + fprintf(fp, "SCAN"); + } else { + fprintf(fp, "ROT"); + } + return true; +} + +static char * +drmGetFormatModifierNameFromArm(uint64_t modifier) +{ + uint64_t type = (modifier >> 52) & 0xf; + + FILE *fp; + size_t size = 0; + char *modifier_name = NULL; + bool result = false; + + fp = open_memstream(&modifier_name, &size); + if (!fp) + return NULL; + + switch (type) { + case DRM_FORMAT_MOD_ARM_TYPE_AFBC: + result = drmGetAfbcFormatModifierNameFromArm(modifier, fp); + break; + case DRM_FORMAT_MOD_ARM_TYPE_AFRC: + result = drmGetAfrcFormatModifierNameFromArm(modifier, fp); + break; + /* misc type is already handled by the static table */ + case DRM_FORMAT_MOD_ARM_TYPE_MISC: + default: + result = false; + break; + } + + fclose(fp); + if (!result) { + free(modifier_name); + return NULL; + } + + return modifier_name; +} + +static char * +drmGetFormatModifierNameFromNvidia(uint64_t modifier) +{ + uint64_t height, kind, gen, sector, compression; + + height = modifier & 0xf; + kind = (modifier >> 12) & 0xff; + + gen = (modifier >> 20) & 0x3; + sector = (modifier >> 22) & 0x1; + compression = (modifier >> 23) & 0x7; + + /* just in case there could other simpler modifiers, not yet added, avoid + * testing against TEGRA_TILE */ + if ((modifier & 0x10) == 0x10) { + char *mod_nvidia; + asprintf(&mod_nvidia, "BLOCK_LINEAR_2D,HEIGHT=%"PRIu64",KIND=%"PRIu64"," + "GEN=%"PRIu64",SECTOR=%"PRIu64",COMPRESSION=%"PRIu64"", height, + kind, gen, sector, compression); + return mod_nvidia; + } + + return NULL; +} + +static void +drmGetFormatModifierNameFromAmdDcc(uint64_t modifier, FILE *fp) +{ + uint64_t dcc_max_compressed_block = + AMD_FMT_MOD_GET(DCC_MAX_COMPRESSED_BLOCK, modifier); + uint64_t dcc_retile = AMD_FMT_MOD_GET(DCC_RETILE, modifier); + + const char *dcc_max_compressed_block_str = NULL; + + fprintf(fp, ",DCC"); + + if (dcc_retile) + fprintf(fp, ",DCC_RETILE"); + + if (!dcc_retile && AMD_FMT_MOD_GET(DCC_PIPE_ALIGN, modifier)) + fprintf(fp, ",DCC_PIPE_ALIGN"); + + if (AMD_FMT_MOD_GET(DCC_INDEPENDENT_64B, modifier)) + fprintf(fp, ",DCC_INDEPENDENT_64B"); + + if (AMD_FMT_MOD_GET(DCC_INDEPENDENT_128B, modifier)) + fprintf(fp, ",DCC_INDEPENDENT_128B"); + + switch (dcc_max_compressed_block) { + case AMD_FMT_MOD_DCC_BLOCK_64B: + dcc_max_compressed_block_str = "64B"; + break; + case AMD_FMT_MOD_DCC_BLOCK_128B: + dcc_max_compressed_block_str = "128B"; + break; + case AMD_FMT_MOD_DCC_BLOCK_256B: + dcc_max_compressed_block_str = "256B"; + break; + } + + if (dcc_max_compressed_block_str) + fprintf(fp, ",DCC_MAX_COMPRESSED_BLOCK=%s", + dcc_max_compressed_block_str); + + if (AMD_FMT_MOD_GET(DCC_CONSTANT_ENCODE, modifier)) + fprintf(fp, ",DCC_CONSTANT_ENCODE"); +} + +static void +drmGetFormatModifierNameFromAmdTile(uint64_t modifier, FILE *fp) +{ + uint64_t pipe_xor_bits, bank_xor_bits, packers, rb; + uint64_t pipe, pipe_align, dcc, dcc_retile, tile_version; + + pipe_align = AMD_FMT_MOD_GET(DCC_PIPE_ALIGN, modifier); + pipe_xor_bits = AMD_FMT_MOD_GET(PIPE_XOR_BITS, modifier); + dcc = AMD_FMT_MOD_GET(DCC, modifier); + dcc_retile = AMD_FMT_MOD_GET(DCC_RETILE, modifier); + tile_version = AMD_FMT_MOD_GET(TILE_VERSION, modifier); + + fprintf(fp, ",PIPE_XOR_BITS=%"PRIu64, pipe_xor_bits); + + if (tile_version == AMD_FMT_MOD_TILE_VER_GFX9) { + bank_xor_bits = AMD_FMT_MOD_GET(BANK_XOR_BITS, modifier); + fprintf(fp, ",BANK_XOR_BITS=%"PRIu64, bank_xor_bits); + } + + if (tile_version == AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS) { + packers = AMD_FMT_MOD_GET(PACKERS, modifier); + fprintf(fp, ",PACKERS=%"PRIu64, packers); + } + + if (dcc && tile_version == AMD_FMT_MOD_TILE_VER_GFX9) { + rb = AMD_FMT_MOD_GET(RB, modifier); + fprintf(fp, ",RB=%"PRIu64, rb); + } + + if (dcc && tile_version == AMD_FMT_MOD_TILE_VER_GFX9 && + (dcc_retile || pipe_align)) { + pipe = AMD_FMT_MOD_GET(PIPE, modifier); + fprintf(fp, ",PIPE_%"PRIu64, pipe); + } +} + +static char * +drmGetFormatModifierNameFromAmd(uint64_t modifier) +{ + uint64_t tile, tile_version, dcc; + FILE *fp; + char *mod_amd = NULL; + size_t size = 0; + + const char *str_tile = NULL; + const char *str_tile_version = NULL; + + tile = AMD_FMT_MOD_GET(TILE, modifier); + tile_version = AMD_FMT_MOD_GET(TILE_VERSION, modifier); + dcc = AMD_FMT_MOD_GET(DCC, modifier); + + fp = open_memstream(&mod_amd, &size); + if (!fp) + return NULL; + + /* add tile */ + switch (tile_version) { + case AMD_FMT_MOD_TILE_VER_GFX9: + str_tile_version = "GFX9"; + break; + case AMD_FMT_MOD_TILE_VER_GFX10: + str_tile_version = "GFX10"; + break; + case AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS: + str_tile_version = "GFX10_RBPLUS"; + break; + } + + if (str_tile_version) { + fprintf(fp, "%s", str_tile_version); + } else { + fclose(fp); + free(mod_amd); + return NULL; + } + + /* add tile str */ + switch (tile) { + case AMD_FMT_MOD_TILE_GFX9_64K_S: + str_tile = "GFX9_64K_S"; + break; + case AMD_FMT_MOD_TILE_GFX9_64K_D: + str_tile = "GFX9_64K_D"; + break; + case AMD_FMT_MOD_TILE_GFX9_64K_S_X: + str_tile = "GFX9_64K_S_X"; + break; + case AMD_FMT_MOD_TILE_GFX9_64K_D_X: + str_tile = "GFX9_64K_D_X"; + break; + case AMD_FMT_MOD_TILE_GFX9_64K_R_X: + str_tile = "GFX9_64K_R_X"; + break; + } + + if (str_tile) + fprintf(fp, ",%s", str_tile); + + if (dcc) + drmGetFormatModifierNameFromAmdDcc(modifier, fp); + + if (tile_version >= AMD_FMT_MOD_TILE_VER_GFX9 && is_x_t_amd_gfx9_tile(tile)) + drmGetFormatModifierNameFromAmdTile(modifier, fp); + + fclose(fp); + return mod_amd; +} + +static char * +drmGetFormatModifierNameFromAmlogic(uint64_t modifier) +{ + uint64_t layout = modifier & 0xff; + uint64_t options = (modifier >> 8) & 0xff; + char *mod_amlogic = NULL; + + const char *layout_str; + const char *opts_str; + + switch (layout) { + case AMLOGIC_FBC_LAYOUT_BASIC: + layout_str = "BASIC"; + break; + case AMLOGIC_FBC_LAYOUT_SCATTER: + layout_str = "SCATTER"; + break; + default: + layout_str = "INVALID_LAYOUT"; + break; + } + + if (options & AMLOGIC_FBC_OPTION_MEM_SAVING) + opts_str = "MEM_SAVING"; + else + opts_str = "0"; + + asprintf(&mod_amlogic, "FBC,LAYOUT=%s,OPTIONS=%s", layout_str, opts_str); + return mod_amlogic; +} + +static unsigned log2_int(unsigned x) +{ + unsigned l; + + if (x < 2) { + return 0; + } + for (l = 2; ; l++) { + if ((unsigned)(1 << l) > x) { + return l - 1; + } + } + return 0; +} + + +drm_public 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 DRM_PRINTFLIKE(1, 0) +drmDebugPrint(const char *format, va_list ap) +{ + return vfprintf(stderr, format, ap); +} + +drm_public void +drmMsg(const char *format, ...) +{ + va_list ap; + const char *env; + if (((env = getenv("LIBGL_DEBUG")) && strstr(env, "verbose")) || + (drm_server_info && drm_server_info->debug_print)) + { + va_start(ap, format); + if (drm_server_info) { + drm_server_info->debug_print(format,ap); + } else { + drmDebugPrint(format, ap); + } + va_end(ap); + } +} + +static void *drmHashTable = NULL; /* Context switch callbacks */ + +drm_public void *drmGetHashTable(void) +{ + return drmHashTable; +} + +drm_public void *drmMalloc(int size) +{ + return calloc(1, size); +} + +drm_public void drmFree(void *pt) +{ + free(pt); +} + +/** + * Call ioctl, restarting if it is interrupted + */ +drm_public 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; +} + +drm_public 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 message using + * drmMsg() call. + */ +#if !UDEV +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; +} +#endif + +static const char *drmGetDeviceName(int type) +{ + switch (type) { + case DRM_NODE_PRIMARY: + return DRM_DEV_NAME; + case DRM_NODE_CONTROL: + return DRM_CONTROL_DEV_NAME; + case DRM_NODE_RENDER: + return DRM_RENDER_DEV_NAME; + } + return NULL; +} + +/** + * 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(dev_t dev, int minor, int type) +{ + stat_t st; + const char *dev_name = drmGetDeviceName(type); + char buf[DRM_NODE_NAME_MAX]; + int fd; + mode_t devmode = DRM_DEV_MODE, serv_mode; + gid_t serv_group; +#if !UDEV + int isroot = !geteuid(); + uid_t user = DRM_DEV_UID; + gid_t group = DRM_DEV_GID; +#endif + + if (!dev_name) + return -EINVAL; + + sprintf(buf, dev_name, DRM_DIR_NAME, minor); + drmMsg("drmOpenDevice: node name is %s\n", buf); + + if (drm_server_info && drm_server_info->get_perms) { + drm_server_info->get_perms(&serv_group, &serv_mode); + devmode = serv_mode ? serv_mode : DRM_DEV_MODE; + devmode &= ~(S_IXUSR|S_IXGRP|S_IXOTH); + } + +#if !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 && drm_server_info->get_perms) { + group = ((int)serv_group >= 0) ? serv_group : DRM_DEV_GID; + 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 | O_CLOEXEC); + drmMsg("drmOpenDevice: open result is %d, (%s)\n", + fd, fd < 0 ? strerror(errno) : "OK"); + if (fd >= 0) + return fd; + +#if !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 && drm_server_info->get_perms) { + chown_check_return(buf, user, group); + chmod(buf, devmode); + } + } + fd = open(buf, O_RDWR | O_CLOEXEC); + 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[DRM_NODE_NAME_MAX]; + const char *dev_name = drmGetDeviceName(type); + + if (create) + return drmOpenDevice(makedev(DRM_MAJOR, minor), minor, type); + + if (!dev_name) + return -EINVAL; + + sprintf(buf, dev_name, DRM_DIR_NAME, minor); + if ((fd = open(buf, O_RDWR | O_CLOEXEC)) >= 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. + */ +drm_public int drmAvailable(void) +{ + drmVersionPtr version; + int retval = 0; + int fd; + + if ((fd = drmOpenMinor(0, 1, DRM_NODE_PRIMARY)) < 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; +} + +static int drmGetMinorBase(int type) +{ + switch (type) { + case DRM_NODE_PRIMARY: + return 0; + case DRM_NODE_CONTROL: + return 64; + case DRM_NODE_RENDER: + return 128; + default: + return -1; + }; +} + +static int drmGetMinorType(int major, int minor) +{ +#ifdef __FreeBSD__ + char name[SPECNAMELEN]; + int id; + + if (!devname_r(makedev(major, minor), S_IFCHR, name, sizeof(name))) + return -1; + + if (sscanf(name, "drm/%d", &id) != 1) { + // If not in /dev/drm/ we have the type in the name + if (sscanf(name, "dri/card%d\n", &id) >= 1) + return DRM_NODE_PRIMARY; + else if (sscanf(name, "dri/control%d\n", &id) >= 1) + return DRM_NODE_CONTROL; + else if (sscanf(name, "dri/renderD%d\n", &id) >= 1) + return DRM_NODE_RENDER; + return -1; + } + + minor = id; +#endif + int type = minor >> 6; + + if (minor < 0) + return -1; + + switch (type) { + case DRM_NODE_PRIMARY: + case DRM_NODE_CONTROL: + case DRM_NODE_RENDER: + return type; + default: + return -1; + } +} + +static const char *drmGetMinorName(int type) +{ + switch (type) { + case DRM_NODE_PRIMARY: + return DRM_PRIMARY_MINOR_NAME; + case DRM_NODE_CONTROL: + return DRM_CONTROL_MINOR_NAME; + case DRM_NODE_RENDER: + return DRM_RENDER_MINOR_NAME; + default: + return NULL; + } +} + +/** + * Open the device by bus ID. + * + * \param busid bus ID. + * \param type device node type. + * + * \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 type) +{ + int i, pci_domain_ok = 1; + int fd; + const char *buf; + drmSetVersion sv; + int base = drmGetMinorBase(type); + + if (base < 0) + return -1; + + drmMsg("drmOpenByBusid: Searching for BusID %s\n", busid); + for (i = base; i < base + DRM_MAX_MINOR; i++) { + fd = drmOpenMinor(i, 1, type); + 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"); + 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. + * \param type the device node type. + * + * \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 type) +{ + int i; + int fd; + drmVersionPtr version; + char * id; + int base = drmGetMinorBase(type); + + if (base < 0) + 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 = base; i < base + DRM_MAX_MINOR; i++) { + if ((fd = drmOpenMinor(i, 1, type)) >= 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, O_RDONLY)) >= 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, type); + } else { /* No busid */ + return drmOpenDevice(strtol(devstring, NULL, 0),i, type); + } + } + } + } + } + } +#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. + */ +drm_public int drmOpen(const char *name, const char *busid) +{ + return drmOpenWithType(name, busid, DRM_NODE_PRIMARY); +} + +/** + * Open the DRM device with specified type. + * + * 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. + * \param type the device node type to open, PRIMARY, CONTROL or RENDER + * + * \return a file descriptor on success, or a negative value on error. + * + * \internal + * It calls drmOpenByBusid() if \p busid is specified or drmOpenByName() + * otherwise. + */ +drm_public int drmOpenWithType(const char *name, const char *busid, int type) +{ + if (name != NULL && drm_server_info && + drm_server_info->load_module && !drmAvailable()) { + /* try to load the kernel module */ + 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, type); + if (fd >= 0) + return fd; + } + + if (name) + return drmOpenByName(name, type); + + return -1; +} + +drm_public int drmOpenControl(int minor) +{ + return drmOpenMinor(minor, 0, DRM_NODE_CONTROL); +} + +drm_public int drmOpenRender(int minor) +{ + return drmOpenMinor(minor, 0, DRM_NODE_RENDER); +} + +/** + * 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. + */ +drm_public 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. + */ +drm_public drmVersionPtr drmGetVersion(int fd) +{ + drmVersionPtr retval; + drm_version_t *version = drmMalloc(sizeof(*version)); + + 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. + */ +drm_public 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<Device> 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; +} + +drm_public int drmGetCap(int fd, uint64_t capability, uint64_t *value) +{ + struct drm_get_cap cap; + int ret; + + memclear(cap); + cap.capability = capability; + + ret = drmIoctl(fd, DRM_IOCTL_GET_CAP, &cap); + if (ret) + return ret; + + *value = cap.value; + return 0; +} + +drm_public int drmSetClientCap(int fd, uint64_t capability, uint64_t value) +{ + struct drm_set_client_cap cap; + + memclear(cap); + cap.capability = capability; + cap.value = value; + + return drmIoctl(fd, DRM_IOCTL_SET_CLIENT_CAP, &cap); +} + +/** + * 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. + */ +drm_public 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. + */ +drm_public char *drmGetBusid(int fd) +{ + drm_unique_t u; + + memclear(u); + + 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)) { + drmFree(u.unique); + 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. + */ +drm_public int drmSetBusid(int fd, const char *busid) +{ + drm_unique_t u; + + memclear(u); + u.unique = (char *)busid; + u.unique_len = strlen(busid); + + if (drmIoctl(fd, DRM_IOCTL_SET_UNIQUE, &u)) { + return -errno; + } + return 0; +} + +drm_public int drmGetMagic(int fd, drm_magic_t * magic) +{ + drm_auth_t auth; + + memclear(auth); + + *magic = 0; + if (drmIoctl(fd, DRM_IOCTL_GET_MAGIC, &auth)) + return -errno; + *magic = auth.magic; + return 0; +} + +drm_public int drmAuthMagic(int fd, drm_magic_t magic) +{ + drm_auth_t auth; + + memclear(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. + */ +drm_public int drmAddMap(int fd, drm_handle_t offset, drmSize size, drmMapType type, + drmMapFlags flags, drm_handle_t *handle) +{ + drm_map_t map; + + memclear(map); + map.offset = offset; + map.size = size; + map.type = (enum drm_map_type)type; + map.flags = (enum drm_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; +} + +drm_public int drmRmMap(int fd, drm_handle_t handle) +{ + drm_map_t map; + + memclear(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. + */ +drm_public int drmAddBufs(int fd, int count, int size, drmBufDescFlags flags, + int agp_offset) +{ + drm_buf_desc_t request; + + memclear(request); + request.count = count; + request.size = size; + request.flags = (int)flags; + request.agp_start = agp_offset; + + if (drmIoctl(fd, DRM_IOCTL_ADD_BUFS, &request)) + return -errno; + return request.count; +} + +drm_public int drmMarkBufs(int fd, double low, double high) +{ + drm_buf_info_t info; + int i; + + memclear(info); + + 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. + * + * \return zero on success, or a negative value on failure. + * + * \note This function is primarily used for debugging. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_FREE_BUFS ioctl, passing + * the arguments in a drm_buf_free structure. + */ +drm_public int drmFreeBufs(int fd, int count, int *list) +{ + drm_buf_free_t request; + + memclear(request); + request.count = count; + request.list = list; + if (drmIoctl(fd, DRM_IOCTL_FREE_BUFS, &request)) + return -errno; + return 0; +} + + +/** + * Close the device. + * + * \param fd file descriptor. + * + * \internal + * This function closes the file descriptor. + */ +drm_public int drmClose(int fd) +{ + unsigned long key = drmGetKeyFromFd(fd); + drmHashEntry *entry = drmGetEntry(fd); + + drmHashDestroy(entry->tagTable); + entry->fd = 0; + entry->f = NULL; + entry->tagTable = NULL; + + drmHashDelete(drmHashTable, key); + drmFree(entry); + + return close(fd); +} + + +/** + * Map a region of memory. + * + * \param fd file descriptor. + * \param handle handle returned by drmAddMap(). + * \param size size in bytes. Must match the size used by drmAddMap(). + * \param address will contain the user-space virtual address where the mapping + * begins. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper for mmap(). + */ +drm_public int drmMap(int fd, drm_handle_t handle, drmSize size, + drmAddressPtr address) +{ + static unsigned long pagesize_mask = 0; + + if (fd < 0) + return -EINVAL; + + if (!pagesize_mask) + pagesize_mask = getpagesize() - 1; + + size = (size + pagesize_mask) & ~pagesize_mask; + + *address = drm_mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, handle); + if (*address == MAP_FAILED) + return -errno; + return 0; +} + + +/** + * Unmap mappings obtained with drmMap(). + * + * \param address address as given by drmMap(). + * \param size size in bytes. Must match the size used by drmMap(). + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper for munmap(). + */ +drm_public int drmUnmap(drmAddress address, drmSize size) +{ + return drm_munmap(address, size); +} + +drm_public drmBufInfoPtr drmGetBufInfo(int fd) +{ + drm_buf_info_t info; + drmBufInfoPtr retval; + int i; + + memclear(info); + + if (drmIoctl(fd, DRM_IOCTL_INFO_BUFS, &info)) + return NULL; + + if (info.count) { + if (!(info.list = drmMalloc(info.count * sizeof(*info.list)))) + return NULL; + + if (drmIoctl(fd, DRM_IOCTL_INFO_BUFS, &info)) { + drmFree(info.list); + return NULL; + } + + retval = drmMalloc(sizeof(*retval)); + retval->count = info.count; + if (!(retval->list = drmMalloc(info.count * sizeof(*retval->list)))) { + drmFree(retval); + drmFree(info.list); + return NULL; + } + + for (i = 0; i < info.count; i++) { + retval->list[i].count = info.list[i].count; + retval->list[i].size = info.list[i].size; + retval->list[i].low_mark = info.list[i].low_mark; + retval->list[i].high_mark = info.list[i].high_mark; + } + drmFree(info.list); + return retval; + } + return NULL; +} + +/** + * Map all DMA buffers into client-virtual space. + * + * \param fd file descriptor. + * + * \return a pointer to a ::drmBufMap structure. + * + * \note The client may not use these buffers until obtaining buffer indices + * with drmDMA(). + * + * \internal + * This function calls the DRM_IOCTL_MAP_BUFS ioctl and copies the returned + * information about the buffers in a drm_buf_map structure into the + * client-visible data structures. + */ +drm_public drmBufMapPtr drmMapBufs(int fd) +{ + drm_buf_map_t bufs; + drmBufMapPtr retval; + int i; + + memclear(bufs); + if (drmIoctl(fd, DRM_IOCTL_MAP_BUFS, &bufs)) + return NULL; + + if (!bufs.count) + return NULL; + + if (!(bufs.list = drmMalloc(bufs.count * sizeof(*bufs.list)))) + return NULL; + + if (drmIoctl(fd, DRM_IOCTL_MAP_BUFS, &bufs)) { + drmFree(bufs.list); + return NULL; + } + + retval = drmMalloc(sizeof(*retval)); + retval->count = bufs.count; + retval->list = drmMalloc(bufs.count * sizeof(*retval->list)); + for (i = 0; i < bufs.count; i++) { + retval->list[i].idx = bufs.list[i].idx; + retval->list[i].total = bufs.list[i].total; + retval->list[i].used = 0; + retval->list[i].address = bufs.list[i].address; + } + + drmFree(bufs.list); + return retval; +} + + +/** + * Unmap buffers allocated with drmMapBufs(). + * + * \return zero on success, or negative value on failure. + * + * \internal + * Calls munmap() for every buffer stored in \p bufs and frees the + * memory allocated by drmMapBufs(). + */ +drm_public int drmUnmapBufs(drmBufMapPtr bufs) +{ + int i; + + for (i = 0; i < bufs->count; i++) { + drm_munmap(bufs->list[i].address, bufs->list[i].total); + } + + drmFree(bufs->list); + drmFree(bufs); + return 0; +} + + +#define DRM_DMA_RETRY 16 + +/** + * Reserve DMA buffers. + * + * \param fd file descriptor. + * \param request + * + * \return zero on success, or a negative value on failure. + * + * \internal + * Assemble the arguments into a drm_dma structure and keeps issuing the + * DRM_IOCTL_DMA ioctl until success or until maximum number of retries. + */ +drm_public int drmDMA(int fd, drmDMAReqPtr request) +{ + drm_dma_t dma; + int ret, i = 0; + + dma.context = request->context; + dma.send_count = request->send_count; + dma.send_indices = request->send_list; + dma.send_sizes = request->send_sizes; + dma.flags = (enum drm_dma_flags)request->flags; + dma.request_count = request->request_count; + dma.request_size = request->request_size; + dma.request_indices = request->request_list; + dma.request_sizes = request->request_sizes; + dma.granted_count = 0; + + do { + ret = ioctl( fd, DRM_IOCTL_DMA, &dma ); + } while ( ret && errno == EAGAIN && i++ < DRM_DMA_RETRY ); + + if ( ret == 0 ) { + request->granted_count = dma.granted_count; + return 0; + } else { + return -errno; + } +} + + +/** + * Obtain heavyweight hardware lock. + * + * \param fd file descriptor. + * \param context context. + * \param flags flags that determine the state of the hardware when the function + * returns. + * + * \return always zero. + * + * \internal + * This function translates the arguments into a drm_lock structure and issue + * the DRM_IOCTL_LOCK ioctl until the lock is successfully acquired. + */ +drm_public int drmGetLock(int fd, drm_context_t context, drmLockFlags flags) +{ + drm_lock_t lock; + + memclear(lock); + lock.context = context; + lock.flags = 0; + if (flags & DRM_LOCK_READY) lock.flags |= _DRM_LOCK_READY; + if (flags & DRM_LOCK_QUIESCENT) lock.flags |= _DRM_LOCK_QUIESCENT; + if (flags & DRM_LOCK_FLUSH) lock.flags |= _DRM_LOCK_FLUSH; + if (flags & DRM_LOCK_FLUSH_ALL) lock.flags |= _DRM_LOCK_FLUSH_ALL; + if (flags & DRM_HALT_ALL_QUEUES) lock.flags |= _DRM_HALT_ALL_QUEUES; + if (flags & DRM_HALT_CUR_QUEUES) lock.flags |= _DRM_HALT_CUR_QUEUES; + + while (drmIoctl(fd, DRM_IOCTL_LOCK, &lock)) + ; + return 0; +} + +/** + * Release the hardware lock. + * + * \param fd file descriptor. + * \param context context. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_UNLOCK ioctl, passing the + * argument in a drm_lock structure. + */ +drm_public int drmUnlock(int fd, drm_context_t context) +{ + drm_lock_t lock; + + memclear(lock); + lock.context = context; + return drmIoctl(fd, DRM_IOCTL_UNLOCK, &lock); +} + +drm_public drm_context_t *drmGetReservedContextList(int fd, int *count) +{ + drm_ctx_res_t res; + drm_ctx_t *list; + drm_context_t * retval; + int i; + + memclear(res); + if (drmIoctl(fd, DRM_IOCTL_RES_CTX, &res)) + return NULL; + + if (!res.count) + return NULL; + + if (!(list = drmMalloc(res.count * sizeof(*list)))) + return NULL; + if (!(retval = drmMalloc(res.count * sizeof(*retval)))) + goto err_free_list; + + res.contexts = list; + if (drmIoctl(fd, DRM_IOCTL_RES_CTX, &res)) + goto err_free_context; + + for (i = 0; i < res.count; i++) + retval[i] = list[i].handle; + drmFree(list); + + *count = res.count; + return retval; + +err_free_list: + drmFree(list); +err_free_context: + drmFree(retval); + return NULL; +} + +drm_public void drmFreeReservedContextList(drm_context_t *pt) +{ + drmFree(pt); +} + +/** + * Create context. + * + * Used by the X server during GLXContext initialization. This causes + * per-context kernel-level resources to be allocated. + * + * \param fd file descriptor. + * \param handle is set on success. To be used by the client when requesting DMA + * dispatch with drmDMA(). + * + * \return zero on success, or a negative value on failure. + * + * \note May only be called by root. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_ADD_CTX ioctl, passing the + * argument in a drm_ctx structure. + */ +drm_public int drmCreateContext(int fd, drm_context_t *handle) +{ + drm_ctx_t ctx; + + memclear(ctx); + if (drmIoctl(fd, DRM_IOCTL_ADD_CTX, &ctx)) + return -errno; + *handle = ctx.handle; + return 0; +} + +drm_public int drmSwitchToContext(int fd, drm_context_t context) +{ + drm_ctx_t ctx; + + memclear(ctx); + ctx.handle = context; + if (drmIoctl(fd, DRM_IOCTL_SWITCH_CTX, &ctx)) + return -errno; + return 0; +} + +drm_public int drmSetContextFlags(int fd, drm_context_t context, + drm_context_tFlags flags) +{ + drm_ctx_t ctx; + + /* + * Context preserving means that no context switches are done between DMA + * buffers from one context and the next. This is suitable for use in the + * X server (which promises to maintain hardware context), or in the + * client-side library when buffers are swapped on behalf of two threads. + */ + memclear(ctx); + ctx.handle = context; + if (flags & DRM_CONTEXT_PRESERVED) + ctx.flags |= _DRM_CONTEXT_PRESERVED; + if (flags & DRM_CONTEXT_2DONLY) + ctx.flags |= _DRM_CONTEXT_2DONLY; + if (drmIoctl(fd, DRM_IOCTL_MOD_CTX, &ctx)) + return -errno; + return 0; +} + +drm_public int drmGetContextFlags(int fd, drm_context_t context, + drm_context_tFlagsPtr flags) +{ + drm_ctx_t ctx; + + memclear(ctx); + ctx.handle = context; + if (drmIoctl(fd, DRM_IOCTL_GET_CTX, &ctx)) + return -errno; + *flags = 0; + if (ctx.flags & _DRM_CONTEXT_PRESERVED) + *flags |= DRM_CONTEXT_PRESERVED; + if (ctx.flags & _DRM_CONTEXT_2DONLY) + *flags |= DRM_CONTEXT_2DONLY; + return 0; +} + +/** + * Destroy context. + * + * Free any kernel-level resources allocated with drmCreateContext() associated + * with the context. + * + * \param fd file descriptor. + * \param handle handle given by drmCreateContext(). + * + * \return zero on success, or a negative value on failure. + * + * \note May only be called by root. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_RM_CTX ioctl, passing the + * argument in a drm_ctx structure. + */ +drm_public int drmDestroyContext(int fd, drm_context_t handle) +{ + drm_ctx_t ctx; + + memclear(ctx); + ctx.handle = handle; + if (drmIoctl(fd, DRM_IOCTL_RM_CTX, &ctx)) + return -errno; + return 0; +} + +drm_public int drmCreateDrawable(int fd, drm_drawable_t *handle) +{ + drm_draw_t draw; + + memclear(draw); + if (drmIoctl(fd, DRM_IOCTL_ADD_DRAW, &draw)) + return -errno; + *handle = draw.handle; + return 0; +} + +drm_public int drmDestroyDrawable(int fd, drm_drawable_t handle) +{ + drm_draw_t draw; + + memclear(draw); + draw.handle = handle; + if (drmIoctl(fd, DRM_IOCTL_RM_DRAW, &draw)) + return -errno; + return 0; +} + +drm_public int drmUpdateDrawableInfo(int fd, drm_drawable_t handle, + drm_drawable_info_type_t type, + unsigned int num, void *data) +{ + drm_update_draw_t update; + + memclear(update); + update.handle = handle; + update.type = type; + update.num = num; + update.data = (unsigned long long)(unsigned long)data; + + if (drmIoctl(fd, DRM_IOCTL_UPDATE_DRAW, &update)) + return -errno; + + return 0; +} + +drm_public int drmCrtcGetSequence(int fd, uint32_t crtcId, uint64_t *sequence, + uint64_t *ns) +{ + struct drm_crtc_get_sequence get_seq; + int ret; + + memclear(get_seq); + get_seq.crtc_id = crtcId; + ret = drmIoctl(fd, DRM_IOCTL_CRTC_GET_SEQUENCE, &get_seq); + if (ret) + return ret; + + if (sequence) + *sequence = get_seq.sequence; + if (ns) + *ns = get_seq.sequence_ns; + return 0; +} + +drm_public int drmCrtcQueueSequence(int fd, uint32_t crtcId, uint32_t flags, + uint64_t sequence, + uint64_t *sequence_queued, + uint64_t user_data) +{ + struct drm_crtc_queue_sequence queue_seq; + int ret; + + memclear(queue_seq); + queue_seq.crtc_id = crtcId; + queue_seq.flags = flags; + queue_seq.sequence = sequence; + queue_seq.user_data = user_data; + + ret = drmIoctl(fd, DRM_IOCTL_CRTC_QUEUE_SEQUENCE, &queue_seq); + if (ret == 0 && sequence_queued) + *sequence_queued = queue_seq.sequence; + + return ret; +} + +/** + * Acquire the AGP device. + * + * Must be called before any of the other AGP related calls. + * + * \param fd file descriptor. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_ACQUIRE ioctl. + */ +drm_public int drmAgpAcquire(int fd) +{ + if (drmIoctl(fd, DRM_IOCTL_AGP_ACQUIRE, NULL)) + return -errno; + return 0; +} + + +/** + * Release the AGP device. + * + * \param fd file descriptor. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_RELEASE ioctl. + */ +drm_public int drmAgpRelease(int fd) +{ + if (drmIoctl(fd, DRM_IOCTL_AGP_RELEASE, NULL)) + return -errno; + return 0; +} + + +/** + * Set the AGP mode. + * + * \param fd file descriptor. + * \param mode AGP mode. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_ENABLE ioctl, passing the + * argument in a drm_agp_mode structure. + */ +drm_public int drmAgpEnable(int fd, unsigned long mode) +{ + drm_agp_mode_t m; + + memclear(m); + m.mode = mode; + if (drmIoctl(fd, DRM_IOCTL_AGP_ENABLE, &m)) + return -errno; + return 0; +} + + +/** + * Allocate a chunk of AGP memory. + * + * \param fd file descriptor. + * \param size requested memory size in bytes. Will be rounded to page boundary. + * \param type type of memory to allocate. + * \param address if not zero, will be set to the physical address of the + * allocated memory. + * \param handle on success will be set to a handle of the allocated memory. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_ALLOC ioctl, passing the + * arguments in a drm_agp_buffer structure. + */ +drm_public int drmAgpAlloc(int fd, unsigned long size, unsigned long type, + unsigned long *address, drm_handle_t *handle) +{ + drm_agp_buffer_t b; + + memclear(b); + *handle = DRM_AGP_NO_HANDLE; + b.size = size; + b.type = type; + if (drmIoctl(fd, DRM_IOCTL_AGP_ALLOC, &b)) + return -errno; + if (address != 0UL) + *address = b.physical; + *handle = b.handle; + return 0; +} + + +/** + * Free a chunk of AGP memory. + * + * \param fd file descriptor. + * \param handle handle to the allocated memory, as given by drmAgpAllocate(). + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_FREE ioctl, passing the + * argument in a drm_agp_buffer structure. + */ +drm_public int drmAgpFree(int fd, drm_handle_t handle) +{ + drm_agp_buffer_t b; + + memclear(b); + b.handle = handle; + if (drmIoctl(fd, DRM_IOCTL_AGP_FREE, &b)) + return -errno; + return 0; +} + + +/** + * Bind a chunk of AGP memory. + * + * \param fd file descriptor. + * \param handle handle to the allocated memory, as given by drmAgpAllocate(). + * \param offset offset in bytes. It will round to page boundary. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_BIND ioctl, passing the + * argument in a drm_agp_binding structure. + */ +drm_public int drmAgpBind(int fd, drm_handle_t handle, unsigned long offset) +{ + drm_agp_binding_t b; + + memclear(b); + b.handle = handle; + b.offset = offset; + if (drmIoctl(fd, DRM_IOCTL_AGP_BIND, &b)) + return -errno; + return 0; +} + + +/** + * Unbind a chunk of AGP memory. + * + * \param fd file descriptor. + * \param handle handle to the allocated memory, as given by drmAgpAllocate(). + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_UNBIND ioctl, passing + * the argument in a drm_agp_binding structure. + */ +drm_public int drmAgpUnbind(int fd, drm_handle_t handle) +{ + drm_agp_binding_t b; + + memclear(b); + b.handle = handle; + if (drmIoctl(fd, DRM_IOCTL_AGP_UNBIND, &b)) + return -errno; + return 0; +} + + +/** + * Get AGP driver major version number. + * + * \param fd file descriptor. + * + * \return major version number on success, or a negative value on failure.. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public int drmAgpVersionMajor(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return -errno; + return i.agp_version_major; +} + + +/** + * Get AGP driver minor version number. + * + * \param fd file descriptor. + * + * \return minor version number on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public int drmAgpVersionMinor(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return -errno; + return i.agp_version_minor; +} + + +/** + * Get AGP mode. + * + * \param fd file descriptor. + * + * \return mode on success, or zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned long drmAgpGetMode(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.mode; +} + + +/** + * Get AGP aperture base. + * + * \param fd file descriptor. + * + * \return aperture base on success, zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned long drmAgpBase(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.aperture_base; +} + + +/** + * Get AGP aperture size. + * + * \param fd file descriptor. + * + * \return aperture size on success, zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned long drmAgpSize(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.aperture_size; +} + + +/** + * Get used AGP memory. + * + * \param fd file descriptor. + * + * \return memory used on success, or zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned long drmAgpMemoryUsed(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.memory_used; +} + + +/** + * Get available AGP memory. + * + * \param fd file descriptor. + * + * \return memory available on success, or zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned long drmAgpMemoryAvail(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.memory_allowed; +} + + +/** + * Get hardware vendor ID. + * + * \param fd file descriptor. + * + * \return vendor ID on success, or zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned int drmAgpVendorId(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.id_vendor; +} + + +/** + * Get hardware device ID. + * + * \param fd file descriptor. + * + * \return zero on success, or zero on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_AGP_INFO ioctl, getting the + * necessary information in a drm_agp_info structure. + */ +drm_public unsigned int drmAgpDeviceId(int fd) +{ + drm_agp_info_t i; + + memclear(i); + + if (drmIoctl(fd, DRM_IOCTL_AGP_INFO, &i)) + return 0; + return i.id_device; +} + +drm_public int drmScatterGatherAlloc(int fd, unsigned long size, + drm_handle_t *handle) +{ + drm_scatter_gather_t sg; + + memclear(sg); + + *handle = 0; + sg.size = size; + if (drmIoctl(fd, DRM_IOCTL_SG_ALLOC, &sg)) + return -errno; + *handle = sg.handle; + return 0; +} + +drm_public int drmScatterGatherFree(int fd, drm_handle_t handle) +{ + drm_scatter_gather_t sg; + + memclear(sg); + sg.handle = handle; + if (drmIoctl(fd, DRM_IOCTL_SG_FREE, &sg)) + return -errno; + return 0; +} + +/** + * Wait for VBLANK. + * + * \param fd file descriptor. + * \param vbl pointer to a drmVBlank structure. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_WAIT_VBLANK ioctl. + */ +drm_public int drmWaitVBlank(int fd, drmVBlankPtr vbl) +{ + struct timespec timeout, cur; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &timeout); + if (ret < 0) { + fprintf(stderr, "clock_gettime failed: %s\n", strerror(errno)); + goto out; + } + timeout.tv_sec++; + + do { + ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl); + vbl->request.type &= ~DRM_VBLANK_RELATIVE; + if (ret && errno == EINTR) { + clock_gettime(CLOCK_MONOTONIC, &cur); + /* Timeout after 1s */ + if (cur.tv_sec > timeout.tv_sec + 1 || + (cur.tv_sec == timeout.tv_sec && cur.tv_nsec >= + timeout.tv_nsec)) { + errno = EBUSY; + ret = -1; + break; + } + } + } while (ret && errno == EINTR); + +out: + return ret; +} + +drm_public int drmError(int err, const char *label) +{ + switch (err) { + case DRM_ERR_NO_DEVICE: + fprintf(stderr, "%s: no device\n", label); + break; + case DRM_ERR_NO_ACCESS: + fprintf(stderr, "%s: no access\n", label); + break; + case DRM_ERR_NOT_ROOT: + fprintf(stderr, "%s: not root\n", label); + break; + case DRM_ERR_INVALID: + fprintf(stderr, "%s: invalid args\n", label); + break; + default: + if (err < 0) + err = -err; + fprintf( stderr, "%s: error %d (%s)\n", label, err, strerror(err) ); + break; + } + + return 1; +} + +/** + * Install IRQ handler. + * + * \param fd file descriptor. + * \param irq IRQ number. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_CONTROL ioctl, passing the + * argument in a drm_control structure. + */ +drm_public int drmCtlInstHandler(int fd, int irq) +{ + drm_control_t ctl; + + memclear(ctl); + ctl.func = DRM_INST_HANDLER; + ctl.irq = irq; + if (drmIoctl(fd, DRM_IOCTL_CONTROL, &ctl)) + return -errno; + return 0; +} + + +/** + * Uninstall IRQ handler. + * + * \param fd file descriptor. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_CONTROL ioctl, passing the + * argument in a drm_control structure. + */ +drm_public int drmCtlUninstHandler(int fd) +{ + drm_control_t ctl; + + memclear(ctl); + ctl.func = DRM_UNINST_HANDLER; + ctl.irq = 0; + if (drmIoctl(fd, DRM_IOCTL_CONTROL, &ctl)) + return -errno; + return 0; +} + +drm_public int drmFinish(int fd, int context, drmLockFlags flags) +{ + drm_lock_t lock; + + memclear(lock); + lock.context = context; + if (flags & DRM_LOCK_READY) lock.flags |= _DRM_LOCK_READY; + if (flags & DRM_LOCK_QUIESCENT) lock.flags |= _DRM_LOCK_QUIESCENT; + if (flags & DRM_LOCK_FLUSH) lock.flags |= _DRM_LOCK_FLUSH; + if (flags & DRM_LOCK_FLUSH_ALL) lock.flags |= _DRM_LOCK_FLUSH_ALL; + if (flags & DRM_HALT_ALL_QUEUES) lock.flags |= _DRM_HALT_ALL_QUEUES; + if (flags & DRM_HALT_CUR_QUEUES) lock.flags |= _DRM_HALT_CUR_QUEUES; + if (drmIoctl(fd, DRM_IOCTL_FINISH, &lock)) + return -errno; + return 0; +} + +/** + * Get IRQ from bus ID. + * + * \param fd file descriptor. + * \param busnum bus number. + * \param devnum device number. + * \param funcnum function number. + * + * \return IRQ number on success, or a negative value on failure. + * + * \internal + * This function is a wrapper around the DRM_IOCTL_IRQ_BUSID ioctl, passing the + * arguments in a drm_irq_busid structure. + */ +drm_public int drmGetInterruptFromBusID(int fd, int busnum, int devnum, + int funcnum) +{ + drm_irq_busid_t p; + + memclear(p); + p.busnum = busnum; + p.devnum = devnum; + p.funcnum = funcnum; + if (drmIoctl(fd, DRM_IOCTL_IRQ_BUSID, &p)) + return -errno; + return p.irq; +} + +drm_public int drmAddContextTag(int fd, drm_context_t context, void *tag) +{ + drmHashEntry *entry = drmGetEntry(fd); + + if (drmHashInsert(entry->tagTable, context, tag)) { + drmHashDelete(entry->tagTable, context); + drmHashInsert(entry->tagTable, context, tag); + } + return 0; +} + +drm_public int drmDelContextTag(int fd, drm_context_t context) +{ + drmHashEntry *entry = drmGetEntry(fd); + + return drmHashDelete(entry->tagTable, context); +} + +drm_public void *drmGetContextTag(int fd, drm_context_t context) +{ + drmHashEntry *entry = drmGetEntry(fd); + void *value; + + if (drmHashLookup(entry->tagTable, context, &value)) + return NULL; + + return value; +} + +drm_public int drmAddContextPrivateMapping(int fd, drm_context_t ctx_id, + drm_handle_t handle) +{ + drm_ctx_priv_map_t map; + + memclear(map); + map.ctx_id = ctx_id; + map.handle = (void *)(uintptr_t)handle; + + if (drmIoctl(fd, DRM_IOCTL_SET_SAREA_CTX, &map)) + return -errno; + return 0; +} + +drm_public int drmGetContextPrivateMapping(int fd, drm_context_t ctx_id, + drm_handle_t *handle) +{ + drm_ctx_priv_map_t map; + + memclear(map); + map.ctx_id = ctx_id; + + if (drmIoctl(fd, DRM_IOCTL_GET_SAREA_CTX, &map)) + return -errno; + if (handle) + *handle = (drm_handle_t)(uintptr_t)map.handle; + + return 0; +} + +drm_public int drmGetMap(int fd, int idx, drm_handle_t *offset, drmSize *size, + drmMapType *type, drmMapFlags *flags, + drm_handle_t *handle, int *mtrr) +{ + drm_map_t map; + + memclear(map); + map.offset = idx; + if (drmIoctl(fd, DRM_IOCTL_GET_MAP, &map)) + return -errno; + *offset = map.offset; + *size = map.size; + *type = (drmMapType)map.type; + *flags = (drmMapFlags)map.flags; + *handle = (unsigned long)map.handle; + *mtrr = map.mtrr; + return 0; +} + +drm_public int drmGetClient(int fd, int idx, int *auth, int *pid, int *uid, + unsigned long *magic, unsigned long *iocs) +{ + drm_client_t client; + + memclear(client); + client.idx = idx; + if (drmIoctl(fd, DRM_IOCTL_GET_CLIENT, &client)) + return -errno; + *auth = client.auth; + *pid = client.pid; + *uid = client.uid; + *magic = client.magic; + *iocs = client.iocs; + return 0; +} + +drm_public int drmGetStats(int fd, drmStatsT *stats) +{ + drm_stats_t s; + unsigned i; + + memclear(s); + if (drmIoctl(fd, DRM_IOCTL_GET_STATS, &s)) + return -errno; + + stats->count = 0; + memset(stats, 0, sizeof(*stats)); + if (s.count > sizeof(stats->data)/sizeof(stats->data[0])) + return -1; + +#define SET_VALUE \ + stats->data[i].long_format = "%-20.20s"; \ + stats->data[i].rate_format = "%8.8s"; \ + stats->data[i].isvalue = 1; \ + stats->data[i].verbose = 0 + +#define SET_COUNT \ + stats->data[i].long_format = "%-20.20s"; \ + stats->data[i].rate_format = "%5.5s"; \ + stats->data[i].isvalue = 0; \ + stats->data[i].mult_names = "kgm"; \ + stats->data[i].mult = 1000; \ + stats->data[i].verbose = 0 + +#define SET_BYTE \ + stats->data[i].long_format = "%-20.20s"; \ + stats->data[i].rate_format = "%5.5s"; \ + stats->data[i].isvalue = 0; \ + stats->data[i].mult_names = "KGM"; \ + stats->data[i].mult = 1024; \ + stats->data[i].verbose = 0 + + + stats->count = s.count; + for (i = 0; i < s.count; i++) { + stats->data[i].value = s.data[i].value; + switch (s.data[i].type) { + case _DRM_STAT_LOCK: + stats->data[i].long_name = "Lock"; + stats->data[i].rate_name = "Lock"; + SET_VALUE; + break; + case _DRM_STAT_OPENS: + stats->data[i].long_name = "Opens"; + stats->data[i].rate_name = "O"; + SET_COUNT; + stats->data[i].verbose = 1; + break; + case _DRM_STAT_CLOSES: + stats->data[i].long_name = "Closes"; + stats->data[i].rate_name = "Lock"; + SET_COUNT; + stats->data[i].verbose = 1; + break; + case _DRM_STAT_IOCTLS: + stats->data[i].long_name = "Ioctls"; + stats->data[i].rate_name = "Ioc/s"; + SET_COUNT; + break; + case _DRM_STAT_LOCKS: + stats->data[i].long_name = "Locks"; + stats->data[i].rate_name = "Lck/s"; + SET_COUNT; + break; + case _DRM_STAT_UNLOCKS: + stats->data[i].long_name = "Unlocks"; + stats->data[i].rate_name = "Unl/s"; + SET_COUNT; + break; + case _DRM_STAT_IRQ: + stats->data[i].long_name = "IRQs"; + stats->data[i].rate_name = "IRQ/s"; + SET_COUNT; + break; + case _DRM_STAT_PRIMARY: + stats->data[i].long_name = "Primary Bytes"; + stats->data[i].rate_name = "PB/s"; + SET_BYTE; + break; + case _DRM_STAT_SECONDARY: + stats->data[i].long_name = "Secondary Bytes"; + stats->data[i].rate_name = "SB/s"; + SET_BYTE; + break; + case _DRM_STAT_DMA: + stats->data[i].long_name = "DMA"; + stats->data[i].rate_name = "DMA/s"; + SET_COUNT; + break; + case _DRM_STAT_SPECIAL: + stats->data[i].long_name = "Special DMA"; + stats->data[i].rate_name = "dma/s"; + SET_COUNT; + break; + case _DRM_STAT_MISSED: + stats->data[i].long_name = "Miss"; + stats->data[i].rate_name = "Ms/s"; + SET_COUNT; + break; + case _DRM_STAT_VALUE: + stats->data[i].long_name = "Value"; + stats->data[i].rate_name = "Value"; + SET_VALUE; + break; + case _DRM_STAT_BYTE: + stats->data[i].long_name = "Bytes"; + stats->data[i].rate_name = "B/s"; + SET_BYTE; + break; + case _DRM_STAT_COUNT: + default: + stats->data[i].long_name = "Count"; + stats->data[i].rate_name = "Cnt/s"; + SET_COUNT; + break; + } + } + return 0; +} + +/** + * Issue a set-version ioctl. + * + * \param fd file descriptor. + * \param drmCommandIndex command index + * \param data source pointer of the data to be read and written. + * \param size size of the data to be read and written. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * It issues a read-write ioctl given by + * \code DRM_COMMAND_BASE + drmCommandIndex \endcode. + */ +drm_public int drmSetInterfaceVersion(int fd, drmSetVersion *version) +{ + int retcode = 0; + drm_set_version_t sv; + + memclear(sv); + sv.drm_di_major = version->drm_di_major; + sv.drm_di_minor = version->drm_di_minor; + sv.drm_dd_major = version->drm_dd_major; + sv.drm_dd_minor = version->drm_dd_minor; + + if (drmIoctl(fd, DRM_IOCTL_SET_VERSION, &sv)) { + retcode = -errno; + } + + version->drm_di_major = sv.drm_di_major; + version->drm_di_minor = sv.drm_di_minor; + version->drm_dd_major = sv.drm_dd_major; + version->drm_dd_minor = sv.drm_dd_minor; + + return retcode; +} + +/** + * Send a device-specific command. + * + * \param fd file descriptor. + * \param drmCommandIndex command index + * + * \return zero on success, or a negative value on failure. + * + * \internal + * It issues a ioctl given by + * \code DRM_COMMAND_BASE + drmCommandIndex \endcode. + */ +drm_public int drmCommandNone(int fd, unsigned long drmCommandIndex) +{ + unsigned long request; + + request = DRM_IO( DRM_COMMAND_BASE + drmCommandIndex); + + if (drmIoctl(fd, request, NULL)) { + return -errno; + } + return 0; +} + + +/** + * Send a device-specific read command. + * + * \param fd file descriptor. + * \param drmCommandIndex command index + * \param data destination pointer of the data to be read. + * \param size size of the data to be read. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * It issues a read ioctl given by + * \code DRM_COMMAND_BASE + drmCommandIndex \endcode. + */ +drm_public int drmCommandRead(int fd, unsigned long drmCommandIndex, + void *data, unsigned long size) +{ + unsigned long request; + + request = DRM_IOC( DRM_IOC_READ, DRM_IOCTL_BASE, + DRM_COMMAND_BASE + drmCommandIndex, size); + + if (drmIoctl(fd, request, data)) { + return -errno; + } + return 0; +} + + +/** + * Send a device-specific write command. + * + * \param fd file descriptor. + * \param drmCommandIndex command index + * \param data source pointer of the data to be written. + * \param size size of the data to be written. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * It issues a write ioctl given by + * \code DRM_COMMAND_BASE + drmCommandIndex \endcode. + */ +drm_public int drmCommandWrite(int fd, unsigned long drmCommandIndex, + void *data, unsigned long size) +{ + unsigned long request; + + request = DRM_IOC( DRM_IOC_WRITE, DRM_IOCTL_BASE, + DRM_COMMAND_BASE + drmCommandIndex, size); + + if (drmIoctl(fd, request, data)) { + return -errno; + } + return 0; +} + + +/** + * Send a device-specific read-write command. + * + * \param fd file descriptor. + * \param drmCommandIndex command index + * \param data source pointer of the data to be read and written. + * \param size size of the data to be read and written. + * + * \return zero on success, or a negative value on failure. + * + * \internal + * It issues a read-write ioctl given by + * \code DRM_COMMAND_BASE + drmCommandIndex \endcode. + */ +drm_public int drmCommandWriteRead(int fd, unsigned long drmCommandIndex, + void *data, unsigned long size) +{ + unsigned long request; + + request = DRM_IOC( DRM_IOC_READ|DRM_IOC_WRITE, DRM_IOCTL_BASE, + DRM_COMMAND_BASE + drmCommandIndex, size); + + if (drmIoctl(fd, request, data)) + return -errno; + return 0; +} + +#define DRM_MAX_FDS 16 +static struct { + char *BusID; + int fd; + int refcount; + int type; +} connection[DRM_MAX_FDS]; + +static int nr_fds = 0; + +drm_public int drmOpenOnce(void *unused, const char *BusID, int *newlyopened) +{ + return drmOpenOnceWithType(BusID, newlyopened, DRM_NODE_PRIMARY); +} + +drm_public int drmOpenOnceWithType(const char *BusID, int *newlyopened, + int type) +{ + int i; + int fd; + + for (i = 0; i < nr_fds; i++) + if ((strcmp(BusID, connection[i].BusID) == 0) && + (connection[i].type == type)) { + connection[i].refcount++; + *newlyopened = 0; + return connection[i].fd; + } + + fd = drmOpenWithType(NULL, BusID, type); + if (fd < 0 || nr_fds == DRM_MAX_FDS) + return fd; + + connection[nr_fds].BusID = strdup(BusID); + connection[nr_fds].fd = fd; + connection[nr_fds].refcount = 1; + connection[nr_fds].type = type; + *newlyopened = 1; + + if (0) + fprintf(stderr, "saved connection %d for %s %d\n", + nr_fds, connection[nr_fds].BusID, + strcmp(BusID, connection[nr_fds].BusID)); + + nr_fds++; + + return fd; +} + +drm_public void drmCloseOnce(int fd) +{ + int i; + + for (i = 0; i < nr_fds; i++) { + if (fd == connection[i].fd) { + if (--connection[i].refcount == 0) { + drmClose(connection[i].fd); + free(connection[i].BusID); + + if (i < --nr_fds) + connection[i] = connection[nr_fds]; + + return; + } + } + } +} + +drm_public int drmSetMaster(int fd) +{ + return drmIoctl(fd, DRM_IOCTL_SET_MASTER, NULL); +} + +drm_public int drmDropMaster(int fd) +{ + return drmIoctl(fd, DRM_IOCTL_DROP_MASTER, NULL); +} + +drm_public int drmIsMaster(int fd) +{ + /* Detect master by attempting something that requires master. + * + * Authenticating magic tokens requires master and 0 is an + * internal kernel detail which we could use. Attempting this on + * a master fd would fail therefore fail with EINVAL because 0 + * is invalid. + * + * A non-master fd will fail with EACCES, as the kernel checks + * for master before attempting to do anything else. + * + * Since we don't want to leak implementation details, use + * EACCES. + */ + return drmAuthMagic(fd, 0) != -EACCES; +} + +drm_public char *drmGetDeviceNameFromFd(int fd) +{ +#ifdef __FreeBSD__ + struct stat sbuf; + int maj, min; + int nodetype; + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + nodetype = drmGetMinorType(maj, min); + return drmGetMinorNameForFD(fd, nodetype); +#else + char name[128]; + struct stat sbuf; + dev_t d; + int i; + + /* The whole drmOpen thing is a fiasco and we need to find a way + * back to just using open(2). For now, however, lets just make + * things worse with even more ad hoc directory walking code to + * discover the device file name. */ + + fstat(fd, &sbuf); + d = sbuf.st_rdev; + + for (i = 0; i < DRM_MAX_MINOR; i++) { + snprintf(name, sizeof name, DRM_DEV_NAME, DRM_DIR_NAME, i); + if (stat(name, &sbuf) == 0 && sbuf.st_rdev == d) + break; + } + if (i == DRM_MAX_MINOR) + return NULL; + + return strdup(name); +#endif +} + +static bool drmNodeIsDRM(int maj, int min) +{ +#ifdef __linux__ + char path[64]; + struct stat sbuf; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device/drm", + maj, min); + return stat(path, &sbuf) == 0; +#elif defined(__FreeBSD__) + char name[SPECNAMELEN]; + + if (!devname_r(makedev(maj, min), S_IFCHR, name, sizeof(name))) + return 0; + /* Handle drm/ and dri/ as both are present in different FreeBSD version + * FreeBSD on amd64/i386/powerpc external kernel modules create node in + * in /dev/drm/ and links in /dev/dri while a WIP in kernel driver creates + * only device nodes in /dev/dri/ */ + return (!strncmp(name, "drm/", 4) || !strncmp(name, "dri/", 4)); +#else + return maj == DRM_MAJOR; +#endif +} + +drm_public int drmGetNodeTypeFromFd(int fd) +{ + struct stat sbuf; + int maj, min, type; + + if (fstat(fd, &sbuf)) + return -1; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) { + errno = EINVAL; + return -1; + } + + type = drmGetMinorType(maj, min); + if (type == -1) + errno = ENODEV; + return type; +} + +drm_public int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, + int *prime_fd) +{ + struct drm_prime_handle args; + int ret; + + memclear(args); + args.fd = -1; + args.handle = handle; + args.flags = flags; + ret = drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args); + if (ret) + return ret; + + *prime_fd = args.fd; + return 0; +} + +drm_public int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) +{ + struct drm_prime_handle args; + int ret; + + memclear(args); + args.fd = prime_fd; + ret = drmIoctl(fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &args); + if (ret) + return ret; + + *handle = args.handle; + return 0; +} + +drm_public int drmCloseBufferHandle(int fd, uint32_t handle) +{ + struct drm_gem_close args; + + memclear(args); + args.handle = handle; + return drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &args); +} + +static char *drmGetMinorNameForFD(int fd, int type) +{ +#ifdef __linux__ + DIR *sysdir; + struct dirent *ent; + struct stat sbuf; + const char *name = drmGetMinorName(type); + int len; + char dev_name[64], buf[64]; + int maj, min; + + if (!name) + return NULL; + + len = strlen(name); + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return NULL; + + snprintf(buf, sizeof(buf), "/sys/dev/char/%d:%d/device/drm", maj, min); + + sysdir = opendir(buf); + if (!sysdir) + return NULL; + + while ((ent = readdir(sysdir))) { + if (strncmp(ent->d_name, name, len) == 0) { + if (snprintf(dev_name, sizeof(dev_name), DRM_DIR_NAME "/%s", + ent->d_name) < 0) + return NULL; + + closedir(sysdir); + return strdup(dev_name); + } + } + + closedir(sysdir); + return NULL; +#elif defined(__FreeBSD__) + struct stat sbuf; + char dname[SPECNAMELEN]; + const char *mname; + char name[SPECNAMELEN]; + int id, maj, min, nodetype, i; + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return NULL; + + if (!devname_r(sbuf.st_rdev, S_IFCHR, dname, sizeof(dname))) + return NULL; + + /* Handle both /dev/drm and /dev/dri + * FreeBSD on amd64/i386/powerpc external kernel modules create node in + * in /dev/drm/ and links in /dev/dri while a WIP in kernel driver creates + * only device nodes in /dev/dri/ */ + + /* Get the node type represented by fd so we can deduce the target name */ + nodetype = drmGetMinorType(maj, min); + if (nodetype == -1) + return (NULL); + mname = drmGetMinorName(type); + + for (i = 0; i < SPECNAMELEN; i++) { + if (isalpha(dname[i]) == 0 && dname[i] != '/') + break; + } + if (dname[i] == '\0') + return (NULL); + + id = (int)strtol(&dname[i], NULL, 10); + id -= drmGetMinorBase(nodetype); + snprintf(name, sizeof(name), DRM_DIR_NAME "/%s%d", mname, + id + drmGetMinorBase(type)); + + return strdup(name); +#else + struct stat sbuf; + char buf[PATH_MAX + 1]; + const char *dev_name = drmGetDeviceName(type); + unsigned int maj, min; + int n; + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return NULL; + + if (!dev_name) + return NULL; + + n = snprintf(buf, sizeof(buf), dev_name, DRM_DIR_NAME, min); + if (n == -1 || n >= sizeof(buf)) + return NULL; + + return strdup(buf); +#endif +} + +drm_public char *drmGetPrimaryDeviceNameFromFd(int fd) +{ + return drmGetMinorNameForFD(fd, DRM_NODE_PRIMARY); +} + +drm_public char *drmGetRenderDeviceNameFromFd(int fd) +{ + return drmGetMinorNameForFD(fd, DRM_NODE_RENDER); +} + +#ifdef __linux__ +static char * DRM_PRINTFLIKE(2, 3) +sysfs_uevent_get(const char *path, const char *fmt, ...) +{ + char filename[PATH_MAX + 1], *key, *line = NULL, *value = NULL; + size_t size = 0, len; + ssize_t num; + va_list ap; + FILE *fp; + + va_start(ap, fmt); + num = vasprintf(&key, fmt, ap); + va_end(ap); + len = num; + + snprintf(filename, sizeof(filename), "%s/uevent", path); + + fp = fopen(filename, "r"); + if (!fp) { + free(key); + return NULL; + } + + while ((num = getline(&line, &size, fp)) >= 0) { + if ((strncmp(line, key, len) == 0) && (line[len] == '=')) { + char *start = line + len + 1, *end = line + num - 1; + + if (*end != '\n') + end++; + + value = strndup(start, end - start); + break; + } + } + + free(line); + fclose(fp); + + free(key); + + return value; +} +#endif + +/* Little white lie to avoid major rework of the existing code */ +#define DRM_BUS_VIRTIO 0x10 + +#ifdef __linux__ +static int get_subsystem_type(const char *device_path) +{ + char path[PATH_MAX + 1] = ""; + char link[PATH_MAX + 1] = ""; + char *name; + struct { + const char *name; + int bus_type; + } bus_types[] = { + { "/pci", DRM_BUS_PCI }, + { "/usb", DRM_BUS_USB }, + { "/platform", DRM_BUS_PLATFORM }, + { "/spi", DRM_BUS_PLATFORM }, + { "/host1x", DRM_BUS_HOST1X }, + { "/virtio", DRM_BUS_VIRTIO }, + }; + + strncpy(path, device_path, PATH_MAX); + strncat(path, "/subsystem", PATH_MAX); + + if (readlink(path, link, PATH_MAX) < 0) + return -errno; + + name = strrchr(link, '/'); + if (!name) + return -EINVAL; + + for (unsigned i = 0; i < ARRAY_SIZE(bus_types); i++) { + if (strncmp(name, bus_types[i].name, strlen(bus_types[i].name)) == 0) + return bus_types[i].bus_type; + } + + return -EINVAL; +} +#endif + +static int drmParseSubsystemType(int maj, int min) +{ +#ifdef __linux__ + char path[PATH_MAX + 1] = ""; + char real_path[PATH_MAX + 1] = ""; + int subsystem_type; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min); + + subsystem_type = get_subsystem_type(path); + /* Try to get the parent (underlying) device type */ + if (subsystem_type == DRM_BUS_VIRTIO) { + /* Assume virtio-pci on error */ + if (!realpath(path, real_path)) + return DRM_BUS_VIRTIO; + strncat(path, "/..", PATH_MAX); + subsystem_type = get_subsystem_type(path); + if (subsystem_type < 0) + return DRM_BUS_VIRTIO; + } + return subsystem_type; +#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) + return DRM_BUS_PCI; +#else +#warning "Missing implementation of drmParseSubsystemType" + return -EINVAL; +#endif +} + +#ifdef __linux__ +static void +get_pci_path(int maj, int min, char *pci_path) +{ + char path[PATH_MAX + 1], *term; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min); + if (!realpath(path, pci_path)) { + strcpy(pci_path, path); + return; + } + + term = strrchr(pci_path, '/'); + if (term && strncmp(term, "/virtio", 7) == 0) + *term = 0; +} +#endif + +#ifdef __FreeBSD__ +static int get_sysctl_pci_bus_info(int maj, int min, drmPciBusInfoPtr info) +{ + char dname[SPECNAMELEN]; + char sysctl_name[16]; + char sysctl_val[256]; + size_t sysctl_len; + int id, type, nelem; + unsigned int rdev, majmin, domain, bus, dev, func; + + rdev = makedev(maj, min); + if (!devname_r(rdev, S_IFCHR, dname, sizeof(dname))) + return -EINVAL; + + if (sscanf(dname, "drm/%d\n", &id) != 1) + return -EINVAL; + type = drmGetMinorType(maj, min); + if (type == -1) + return -EINVAL; + + /* BUG: This above section is iffy, since it mandates that a driver will + * create both card and render node. + * If it does not, the next DRM device will create card#X and + * renderD#(128+X)-1. + * This is a possibility in FreeBSD but for now there is no good way for + * obtaining the info. + */ + switch (type) { + case DRM_NODE_PRIMARY: + break; + case DRM_NODE_CONTROL: + id -= 64; + break; + case DRM_NODE_RENDER: + id -= 128; + break; + } + if (id < 0) + return -EINVAL; + + if (snprintf(sysctl_name, sizeof(sysctl_name), "hw.dri.%d.busid", id) <= 0) + return -EINVAL; + sysctl_len = sizeof(sysctl_val); + if (sysctlbyname(sysctl_name, sysctl_val, &sysctl_len, NULL, 0)) + return -EINVAL; + + #define bus_fmt "pci:%04x:%02x:%02x.%u" + + nelem = sscanf(sysctl_val, bus_fmt, &domain, &bus, &dev, &func); + if (nelem != 4) + return -EINVAL; + info->domain = domain; + info->bus = bus; + info->dev = dev; + info->func = func; + + return 0; +} +#endif + +static int drmParsePciBusInfo(int maj, int min, drmPciBusInfoPtr info) +{ +#ifdef __linux__ + unsigned int domain, bus, dev, func; + char pci_path[PATH_MAX + 1], *value; + int num; + + get_pci_path(maj, min, pci_path); + + value = sysfs_uevent_get(pci_path, "PCI_SLOT_NAME"); + if (!value) + return -ENOENT; + + num = sscanf(value, "%04x:%02x:%02x.%1u", &domain, &bus, &dev, &func); + free(value); + + if (num != 4) + return -EINVAL; + + info->domain = domain; + info->bus = bus; + info->dev = dev; + info->func = func; + + return 0; +#elif defined(__OpenBSD__) || defined(__DragonFly__) + struct drm_pciinfo pinfo; + int fd, type; + + type = drmGetMinorType(maj, min); + if (type == -1) + return -ENODEV; + + fd = drmOpenMinor(min, 0, type); + if (fd < 0) + return -errno; + + if (drmIoctl(fd, DRM_IOCTL_GET_PCIINFO, &pinfo)) { + close(fd); + return -errno; + } + close(fd); + + info->domain = pinfo.domain; + info->bus = pinfo.bus; + info->dev = pinfo.dev; + info->func = pinfo.func; + + return 0; +#elif defined(__FreeBSD__) + return get_sysctl_pci_bus_info(maj, min, info); +#else +#warning "Missing implementation of drmParsePciBusInfo" + return -EINVAL; +#endif +} + +drm_public int drmDevicesEqual(drmDevicePtr a, drmDevicePtr b) +{ + if (a == NULL || b == NULL) + return 0; + + if (a->bustype != b->bustype) + return 0; + + switch (a->bustype) { + case DRM_BUS_PCI: + return memcmp(a->businfo.pci, b->businfo.pci, sizeof(drmPciBusInfo)) == 0; + + case DRM_BUS_USB: + return memcmp(a->businfo.usb, b->businfo.usb, sizeof(drmUsbBusInfo)) == 0; + + case DRM_BUS_PLATFORM: + return memcmp(a->businfo.platform, b->businfo.platform, sizeof(drmPlatformBusInfo)) == 0; + + case DRM_BUS_HOST1X: + return memcmp(a->businfo.host1x, b->businfo.host1x, sizeof(drmHost1xBusInfo)) == 0; + + default: + break; + } + + return 0; +} + +static int drmGetNodeType(const char *name) +{ + if (strncmp(name, DRM_CONTROL_MINOR_NAME, + sizeof(DRM_CONTROL_MINOR_NAME ) - 1) == 0) + return DRM_NODE_CONTROL; + + if (strncmp(name, DRM_RENDER_MINOR_NAME, + sizeof(DRM_RENDER_MINOR_NAME) - 1) == 0) + return DRM_NODE_RENDER; + + if (strncmp(name, DRM_PRIMARY_MINOR_NAME, + sizeof(DRM_PRIMARY_MINOR_NAME) - 1) == 0) + return DRM_NODE_PRIMARY; + + return -EINVAL; +} + +static int drmGetMaxNodeName(void) +{ + return sizeof(DRM_DIR_NAME) + + MAX3(sizeof(DRM_PRIMARY_MINOR_NAME), + sizeof(DRM_CONTROL_MINOR_NAME), + sizeof(DRM_RENDER_MINOR_NAME)) + + 3 /* length of the node number */; +} + +#ifdef __linux__ +static int parse_separate_sysfs_files(int maj, int min, + drmPciDeviceInfoPtr device, + bool ignore_revision) +{ + static const char *attrs[] = { + "revision", /* Older kernels are missing the file, so check for it first */ + "vendor", + "device", + "subsystem_vendor", + "subsystem_device", + }; + char path[PATH_MAX + 1], pci_path[PATH_MAX + 1]; + unsigned int data[ARRAY_SIZE(attrs)]; + FILE *fp; + int ret; + + get_pci_path(maj, min, pci_path); + + for (unsigned i = ignore_revision ? 1 : 0; i < ARRAY_SIZE(attrs); i++) { + if (snprintf(path, PATH_MAX, "%s/%s", pci_path, attrs[i]) < 0) + return -errno; + + fp = fopen(path, "r"); + if (!fp) + return -errno; + + ret = fscanf(fp, "%x", &data[i]); + fclose(fp); + if (ret != 1) + return -errno; + + } + + device->revision_id = ignore_revision ? 0xff : data[0] & 0xff; + device->vendor_id = data[1] & 0xffff; + device->device_id = data[2] & 0xffff; + device->subvendor_id = data[3] & 0xffff; + device->subdevice_id = data[4] & 0xffff; + + return 0; +} + +static int parse_config_sysfs_file(int maj, int min, + drmPciDeviceInfoPtr device) +{ + char path[PATH_MAX + 1], pci_path[PATH_MAX + 1]; + unsigned char config[64]; + int fd, ret; + + get_pci_path(maj, min, pci_path); + + if (snprintf(path, PATH_MAX, "%s/config", pci_path) < 0) + return -errno; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + ret = read(fd, config, sizeof(config)); + close(fd); + if (ret < 0) + return -errno; + + device->vendor_id = config[0] | (config[1] << 8); + device->device_id = config[2] | (config[3] << 8); + device->revision_id = config[8]; + device->subvendor_id = config[44] | (config[45] << 8); + device->subdevice_id = config[46] | (config[47] << 8); + + return 0; +} +#endif + +static int drmParsePciDeviceInfo(int maj, int min, + drmPciDeviceInfoPtr device, + uint32_t flags) +{ +#ifdef __linux__ + if (!(flags & DRM_DEVICE_GET_PCI_REVISION)) + return parse_separate_sysfs_files(maj, min, device, true); + + if (parse_separate_sysfs_files(maj, min, device, false)) + return parse_config_sysfs_file(maj, min, device); + + return 0; +#elif defined(__OpenBSD__) || defined(__DragonFly__) + struct drm_pciinfo pinfo; + int fd, type; + + type = drmGetMinorType(maj, min); + if (type == -1) + return -ENODEV; + + fd = drmOpenMinor(min, 0, type); + if (fd < 0) + return -errno; + + if (drmIoctl(fd, DRM_IOCTL_GET_PCIINFO, &pinfo)) { + close(fd); + return -errno; + } + close(fd); + + device->vendor_id = pinfo.vendor_id; + device->device_id = pinfo.device_id; + device->revision_id = pinfo.revision_id; + device->subvendor_id = pinfo.subvendor_id; + device->subdevice_id = pinfo.subdevice_id; + + return 0; +#elif defined(__FreeBSD__) + drmPciBusInfo info; + struct pci_conf_io pc; + struct pci_match_conf patterns[1]; + struct pci_conf results[1]; + int fd, error; + + if (get_sysctl_pci_bus_info(maj, min, &info) != 0) + return -EINVAL; + + fd = open("/dev/pci", O_RDONLY); + if (fd < 0) + return -errno; + + bzero(&patterns, sizeof(patterns)); + patterns[0].pc_sel.pc_domain = info.domain; + patterns[0].pc_sel.pc_bus = info.bus; + patterns[0].pc_sel.pc_dev = info.dev; + patterns[0].pc_sel.pc_func = info.func; + patterns[0].flags = PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS + | PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC; + bzero(&pc, sizeof(struct pci_conf_io)); + pc.num_patterns = 1; + pc.pat_buf_len = sizeof(patterns); + pc.patterns = patterns; + pc.match_buf_len = sizeof(results); + pc.matches = results; + + if (ioctl(fd, PCIOCGETCONF, &pc) || pc.status == PCI_GETCONF_ERROR) { + error = errno; + close(fd); + return -error; + } + close(fd); + + device->vendor_id = results[0].pc_vendor; + device->device_id = results[0].pc_device; + device->subvendor_id = results[0].pc_subvendor; + device->subdevice_id = results[0].pc_subdevice; + device->revision_id = results[0].pc_revid; + + return 0; +#else +#warning "Missing implementation of drmParsePciDeviceInfo" + return -EINVAL; +#endif +} + +static void drmFreePlatformDevice(drmDevicePtr device) +{ + if (device->deviceinfo.platform) { + if (device->deviceinfo.platform->compatible) { + char **compatible = device->deviceinfo.platform->compatible; + + while (*compatible) { + free(*compatible); + compatible++; + } + + free(device->deviceinfo.platform->compatible); + } + } +} + +static void drmFreeHost1xDevice(drmDevicePtr device) +{ + if (device->deviceinfo.host1x) { + if (device->deviceinfo.host1x->compatible) { + char **compatible = device->deviceinfo.host1x->compatible; + + while (*compatible) { + free(*compatible); + compatible++; + } + + free(device->deviceinfo.host1x->compatible); + } + } +} + +drm_public void drmFreeDevice(drmDevicePtr *device) +{ + if (device == NULL) + return; + + if (*device) { + switch ((*device)->bustype) { + case DRM_BUS_PLATFORM: + drmFreePlatformDevice(*device); + break; + + case DRM_BUS_HOST1X: + drmFreeHost1xDevice(*device); + break; + } + } + + free(*device); + *device = NULL; +} + +drm_public void drmFreeDevices(drmDevicePtr devices[], int count) +{ + int i; + + if (devices == NULL) + return; + + for (i = 0; i < count; i++) + if (devices[i]) + drmFreeDevice(&devices[i]); +} + +static drmDevicePtr drmDeviceAlloc(unsigned int type, const char *node, + size_t bus_size, size_t device_size, + char **ptrp) +{ + size_t max_node_length, extra, size; + drmDevicePtr device; + unsigned int i; + char *ptr; + + max_node_length = ALIGN(drmGetMaxNodeName(), sizeof(void *)); + extra = DRM_NODE_MAX * (sizeof(void *) + max_node_length); + + size = sizeof(*device) + extra + bus_size + device_size; + + device = calloc(1, size); + if (!device) + return NULL; + + device->available_nodes = 1 << type; + + ptr = (char *)device + sizeof(*device); + device->nodes = (char **)ptr; + + ptr += DRM_NODE_MAX * sizeof(void *); + + for (i = 0; i < DRM_NODE_MAX; i++) { + device->nodes[i] = ptr; + ptr += max_node_length; + } + + memcpy(device->nodes[type], node, max_node_length); + + *ptrp = ptr; + + return device; +} + +static int drmProcessPciDevice(drmDevicePtr *device, + const char *node, int node_type, + int maj, int min, bool fetch_deviceinfo, + uint32_t flags) +{ + drmDevicePtr dev; + char *addr; + int ret; + + dev = drmDeviceAlloc(node_type, node, sizeof(drmPciBusInfo), + sizeof(drmPciDeviceInfo), &addr); + if (!dev) + return -ENOMEM; + + dev->bustype = DRM_BUS_PCI; + + dev->businfo.pci = (drmPciBusInfoPtr)addr; + + ret = drmParsePciBusInfo(maj, min, dev->businfo.pci); + if (ret) + goto free_device; + + // Fetch the device info if the user has requested it + if (fetch_deviceinfo) { + addr += sizeof(drmPciBusInfo); + dev->deviceinfo.pci = (drmPciDeviceInfoPtr)addr; + + ret = drmParsePciDeviceInfo(maj, min, dev->deviceinfo.pci, flags); + if (ret) + goto free_device; + } + + *device = dev; + + return 0; + +free_device: + free(dev); + return ret; +} + +#ifdef __linux__ +static int drm_usb_dev_path(int maj, int min, char *path, size_t len) +{ + char *value, *tmp_path, *slash; + bool usb_device, usb_interface; + + snprintf(path, len, "/sys/dev/char/%d:%d/device", maj, min); + + value = sysfs_uevent_get(path, "DEVTYPE"); + if (!value) + return -ENOENT; + + usb_device = strcmp(value, "usb_device") == 0; + usb_interface = strcmp(value, "usb_interface") == 0; + free(value); + + if (usb_device) + return 0; + if (!usb_interface) + return -ENOTSUP; + + /* The parent of a usb_interface is a usb_device */ + + tmp_path = realpath(path, NULL); + if (!tmp_path) + return -errno; + + slash = strrchr(tmp_path, '/'); + if (!slash) { + free(tmp_path); + return -EINVAL; + } + + *slash = '\0'; + + if (snprintf(path, len, "%s", tmp_path) >= (int)len) { + free(tmp_path); + return -EINVAL; + } + + free(tmp_path); + return 0; +} +#endif + +static int drmParseUsbBusInfo(int maj, int min, drmUsbBusInfoPtr info) +{ +#ifdef __linux__ + char path[PATH_MAX + 1], *value; + unsigned int bus, dev; + int ret; + + ret = drm_usb_dev_path(maj, min, path, sizeof(path)); + if (ret < 0) + return ret; + + value = sysfs_uevent_get(path, "BUSNUM"); + if (!value) + return -ENOENT; + + ret = sscanf(value, "%03u", &bus); + free(value); + + if (ret <= 0) + return -errno; + + value = sysfs_uevent_get(path, "DEVNUM"); + if (!value) + return -ENOENT; + + ret = sscanf(value, "%03u", &dev); + free(value); + + if (ret <= 0) + return -errno; + + info->bus = bus; + info->dev = dev; + + return 0; +#else +#warning "Missing implementation of drmParseUsbBusInfo" + return -EINVAL; +#endif +} + +static int drmParseUsbDeviceInfo(int maj, int min, drmUsbDeviceInfoPtr info) +{ +#ifdef __linux__ + char path[PATH_MAX + 1], *value; + unsigned int vendor, product; + int ret; + + ret = drm_usb_dev_path(maj, min, path, sizeof(path)); + if (ret < 0) + return ret; + + value = sysfs_uevent_get(path, "PRODUCT"); + if (!value) + return -ENOENT; + + ret = sscanf(value, "%x/%x", &vendor, &product); + free(value); + + if (ret <= 0) + return -errno; + + info->vendor = vendor; + info->product = product; + + return 0; +#else +#warning "Missing implementation of drmParseUsbDeviceInfo" + return -EINVAL; +#endif +} + +static int drmProcessUsbDevice(drmDevicePtr *device, const char *node, + int node_type, int maj, int min, + bool fetch_deviceinfo, uint32_t flags) +{ + drmDevicePtr dev; + char *ptr; + int ret; + + dev = drmDeviceAlloc(node_type, node, sizeof(drmUsbBusInfo), + sizeof(drmUsbDeviceInfo), &ptr); + if (!dev) + return -ENOMEM; + + dev->bustype = DRM_BUS_USB; + + dev->businfo.usb = (drmUsbBusInfoPtr)ptr; + + ret = drmParseUsbBusInfo(maj, min, dev->businfo.usb); + if (ret < 0) + goto free_device; + + if (fetch_deviceinfo) { + ptr += sizeof(drmUsbBusInfo); + dev->deviceinfo.usb = (drmUsbDeviceInfoPtr)ptr; + + ret = drmParseUsbDeviceInfo(maj, min, dev->deviceinfo.usb); + if (ret < 0) + goto free_device; + } + + *device = dev; + + return 0; + +free_device: + free(dev); + return ret; +} + +static int drmParseOFBusInfo(int maj, int min, char *fullname) +{ +#ifdef __linux__ + char path[PATH_MAX + 1], *name, *tmp_name; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min); + + name = sysfs_uevent_get(path, "OF_FULLNAME"); + tmp_name = name; + if (!name) { + /* If the device lacks OF data, pick the MODALIAS info */ + name = sysfs_uevent_get(path, "MODALIAS"); + if (!name) + return -ENOENT; + + /* .. and strip the MODALIAS=[platform,usb...]: part. */ + tmp_name = strrchr(name, ':'); + if (!tmp_name) { + free(name); + return -ENOENT; + } + tmp_name++; + } + + strncpy(fullname, tmp_name, DRM_PLATFORM_DEVICE_NAME_LEN); + fullname[DRM_PLATFORM_DEVICE_NAME_LEN - 1] = '\0'; + free(name); + + return 0; +#else +#warning "Missing implementation of drmParseOFBusInfo" + return -EINVAL; +#endif +} + +static int drmParseOFDeviceInfo(int maj, int min, char ***compatible) +{ +#ifdef __linux__ + char path[PATH_MAX + 1], *value, *tmp_name; + unsigned int count, i; + int err; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min); + + value = sysfs_uevent_get(path, "OF_COMPATIBLE_N"); + if (value) { + sscanf(value, "%u", &count); + free(value); + } else { + /* Assume one entry if the device lack OF data */ + count = 1; + } + + *compatible = calloc(count + 1, sizeof(char *)); + if (!*compatible) + return -ENOMEM; + + for (i = 0; i < count; i++) { + value = sysfs_uevent_get(path, "OF_COMPATIBLE_%u", i); + tmp_name = value; + if (!value) { + /* If the device lacks OF data, pick the MODALIAS info */ + value = sysfs_uevent_get(path, "MODALIAS"); + if (!value) { + err = -ENOENT; + goto free; + } + + /* .. and strip the MODALIAS=[platform,usb...]: part. */ + tmp_name = strrchr(value, ':'); + if (!tmp_name) { + free(value); + return -ENOENT; + } + tmp_name = strdup(tmp_name + 1); + free(value); + } + + (*compatible)[i] = tmp_name; + } + + return 0; + +free: + while (i--) + free((*compatible)[i]); + + free(*compatible); + return err; +#else +#warning "Missing implementation of drmParseOFDeviceInfo" + return -EINVAL; +#endif +} + +static int drmProcessPlatformDevice(drmDevicePtr *device, + const char *node, int node_type, + int maj, int min, bool fetch_deviceinfo, + uint32_t flags) +{ + drmDevicePtr dev; + char *ptr; + int ret; + + dev = drmDeviceAlloc(node_type, node, sizeof(drmPlatformBusInfo), + sizeof(drmPlatformDeviceInfo), &ptr); + if (!dev) + return -ENOMEM; + + dev->bustype = DRM_BUS_PLATFORM; + + dev->businfo.platform = (drmPlatformBusInfoPtr)ptr; + + ret = drmParseOFBusInfo(maj, min, dev->businfo.platform->fullname); + if (ret < 0) + goto free_device; + + if (fetch_deviceinfo) { + ptr += sizeof(drmPlatformBusInfo); + dev->deviceinfo.platform = (drmPlatformDeviceInfoPtr)ptr; + + ret = drmParseOFDeviceInfo(maj, min, &dev->deviceinfo.platform->compatible); + if (ret < 0) + goto free_device; + } + + *device = dev; + + return 0; + +free_device: + free(dev); + return ret; +} + +static int drmProcessHost1xDevice(drmDevicePtr *device, + const char *node, int node_type, + int maj, int min, bool fetch_deviceinfo, + uint32_t flags) +{ + drmDevicePtr dev; + char *ptr; + int ret; + + dev = drmDeviceAlloc(node_type, node, sizeof(drmHost1xBusInfo), + sizeof(drmHost1xDeviceInfo), &ptr); + if (!dev) + return -ENOMEM; + + dev->bustype = DRM_BUS_HOST1X; + + dev->businfo.host1x = (drmHost1xBusInfoPtr)ptr; + + ret = drmParseOFBusInfo(maj, min, dev->businfo.host1x->fullname); + if (ret < 0) + goto free_device; + + if (fetch_deviceinfo) { + ptr += sizeof(drmHost1xBusInfo); + dev->deviceinfo.host1x = (drmHost1xDeviceInfoPtr)ptr; + + ret = drmParseOFDeviceInfo(maj, min, &dev->deviceinfo.host1x->compatible); + if (ret < 0) + goto free_device; + } + + *device = dev; + + return 0; + +free_device: + free(dev); + return ret; +} + +static int +process_device(drmDevicePtr *device, const char *d_name, + int req_subsystem_type, + bool fetch_deviceinfo, uint32_t flags) +{ + struct stat sbuf; + char node[PATH_MAX + 1]; + int node_type, subsystem_type; + unsigned int maj, min; + + node_type = drmGetNodeType(d_name); + if (node_type < 0) + return -1; + + snprintf(node, PATH_MAX, "%s/%s", DRM_DIR_NAME, d_name); + if (stat(node, &sbuf)) + return -1; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return -1; + + subsystem_type = drmParseSubsystemType(maj, min); + if (req_subsystem_type != -1 && req_subsystem_type != subsystem_type) + return -1; + + switch (subsystem_type) { + case DRM_BUS_PCI: + case DRM_BUS_VIRTIO: + return drmProcessPciDevice(device, node, node_type, maj, min, + fetch_deviceinfo, flags); + case DRM_BUS_USB: + return drmProcessUsbDevice(device, node, node_type, maj, min, + fetch_deviceinfo, flags); + case DRM_BUS_PLATFORM: + return drmProcessPlatformDevice(device, node, node_type, maj, min, + fetch_deviceinfo, flags); + case DRM_BUS_HOST1X: + return drmProcessHost1xDevice(device, node, node_type, maj, min, + fetch_deviceinfo, flags); + default: + return -1; + } +} + +/* Consider devices located on the same bus as duplicate and fold the respective + * entries into a single one. + * + * Note: this leaves "gaps" in the array, while preserving the length. + */ +static void drmFoldDuplicatedDevices(drmDevicePtr local_devices[], int count) +{ + int node_type, i, j; + + for (i = 0; i < count; i++) { + for (j = i + 1; j < count; j++) { + if (drmDevicesEqual(local_devices[i], local_devices[j])) { + local_devices[i]->available_nodes |= local_devices[j]->available_nodes; + node_type = log2_int(local_devices[j]->available_nodes); + memcpy(local_devices[i]->nodes[node_type], + local_devices[j]->nodes[node_type], drmGetMaxNodeName()); + drmFreeDevice(&local_devices[j]); + } + } + } +} + +/* Check that the given flags are valid returning 0 on success */ +static int +drm_device_validate_flags(uint32_t flags) +{ + return (flags & ~DRM_DEVICE_GET_PCI_REVISION); +} + +static bool +drm_device_has_rdev(drmDevicePtr device, dev_t find_rdev) +{ + struct stat sbuf; + + for (int i = 0; i < DRM_NODE_MAX; i++) { + if (device->available_nodes & 1 << i) { + if (stat(device->nodes[i], &sbuf) == 0 && + sbuf.st_rdev == find_rdev) + return true; + } + } + return false; +} + +/* + * The kernel drm core has a number of places that assume maximum of + * 3x64 devices nodes. That's 64 for each of primary, control and + * render nodes. Rounded it up to 256 for simplicity. + */ +#define MAX_DRM_NODES 256 + +/** + * Get information about a device from its dev_t identifier + * + * \param find_rdev dev_t identifier of the device + * \param flags feature/behaviour bitmask + * \param device the address of a drmDevicePtr where the information + * will be allocated in stored + * + * \return zero on success, negative error code otherwise. + */ +drm_public int drmGetDeviceFromDevId(dev_t find_rdev, uint32_t flags, drmDevicePtr *device) +{ +#ifdef __OpenBSD__ + /* + * DRI device nodes on OpenBSD are not in their own directory, they reside + * in /dev along with a large number of statically generated /dev nodes. + * Avoid stat'ing all of /dev needlessly by implementing this custom path. + */ + drmDevicePtr d; + char node[PATH_MAX + 1]; + const char *dev_name; + int node_type, subsystem_type; + int maj, min, n, ret; + + if (device == NULL) + return -EINVAL; + + maj = major(find_rdev); + min = minor(find_rdev); + + if (!drmNodeIsDRM(maj, min)) + return -EINVAL; + + node_type = drmGetMinorType(maj, min); + if (node_type == -1) + return -ENODEV; + + dev_name = drmGetDeviceName(node_type); + if (!dev_name) + return -EINVAL; + + n = snprintf(node, PATH_MAX, dev_name, DRM_DIR_NAME, min); + if (n == -1 || n >= PATH_MAX) + return -errno; + if (stat(node, &sbuf)) + return -EINVAL; + + subsystem_type = drmParseSubsystemType(maj, min); + if (subsystem_type != DRM_BUS_PCI) + return -ENODEV; + + ret = drmProcessPciDevice(&d, node, node_type, maj, min, true, flags); + if (ret) + return ret; + + *device = d; + + return 0; +#else + drmDevicePtr local_devices[MAX_DRM_NODES]; + drmDevicePtr d; + DIR *sysdir; + struct dirent *dent; + int subsystem_type; + int maj, min; + int ret, i, node_count; + + if (drm_device_validate_flags(flags)) + return -EINVAL; + + if (device == NULL) + return -EINVAL; + + maj = major(find_rdev); + min = minor(find_rdev); + + if (!drmNodeIsDRM(maj, min)) + return -EINVAL; + + subsystem_type = drmParseSubsystemType(maj, min); + if (subsystem_type < 0) + return subsystem_type; + + sysdir = opendir(DRM_DIR_NAME); + if (!sysdir) + return -errno; + + i = 0; + while ((dent = readdir(sysdir))) { + ret = process_device(&d, dent->d_name, subsystem_type, true, flags); + if (ret) + continue; + + if (i >= MAX_DRM_NODES) { + fprintf(stderr, "More than %d drm nodes detected. " + "Please report a bug - that should not happen.\n" + "Skipping extra nodes\n", MAX_DRM_NODES); + break; + } + local_devices[i] = d; + i++; + } + node_count = i; + + drmFoldDuplicatedDevices(local_devices, node_count); + + *device = NULL; + + for (i = 0; i < node_count; i++) { + if (!local_devices[i]) + continue; + + if (drm_device_has_rdev(local_devices[i], find_rdev)) + *device = local_devices[i]; + else + drmFreeDevice(&local_devices[i]); + } + + closedir(sysdir); + if (*device == NULL) + return -ENODEV; + return 0; +#endif +} + +/** + * Get information about the opened drm device + * + * \param fd file descriptor of the drm device + * \param flags feature/behaviour bitmask + * \param device the address of a drmDevicePtr where the information + * will be allocated in stored + * + * \return zero on success, negative error code otherwise. + * + * \note Unlike drmGetDevice it does not retrieve the pci device revision field + * unless the DRM_DEVICE_GET_PCI_REVISION \p flag is set. + */ +drm_public int drmGetDevice2(int fd, uint32_t flags, drmDevicePtr *device) +{ + struct stat sbuf; + + if (fd == -1) + return -EINVAL; + + if (fstat(fd, &sbuf)) + return -errno; + + if (!S_ISCHR(sbuf.st_mode)) + return -EINVAL; + + return drmGetDeviceFromDevId(sbuf.st_rdev, flags, device); +} + +/** + * Get information about the opened drm device + * + * \param fd file descriptor of the drm device + * \param device the address of a drmDevicePtr where the information + * will be allocated in stored + * + * \return zero on success, negative error code otherwise. + */ +drm_public int drmGetDevice(int fd, drmDevicePtr *device) +{ + return drmGetDevice2(fd, DRM_DEVICE_GET_PCI_REVISION, device); +} + +/** + * Get drm devices on the system + * + * \param flags feature/behaviour bitmask + * \param devices the array of devices with drmDevicePtr elements + * can be NULL to get the device number first + * \param max_devices the maximum number of devices for the array + * + * \return on error - negative error code, + * if devices is NULL - total number of devices available on the system, + * alternatively the number of devices stored in devices[], which is + * capped by the max_devices. + * + * \note Unlike drmGetDevices it does not retrieve the pci device revision field + * unless the DRM_DEVICE_GET_PCI_REVISION \p flag is set. + */ +drm_public int drmGetDevices2(uint32_t flags, drmDevicePtr devices[], + int max_devices) +{ + drmDevicePtr local_devices[MAX_DRM_NODES]; + drmDevicePtr device; + DIR *sysdir; + struct dirent *dent; + int ret, i, node_count, device_count; + + if (drm_device_validate_flags(flags)) + return -EINVAL; + + sysdir = opendir(DRM_DIR_NAME); + if (!sysdir) + return -errno; + + i = 0; + while ((dent = readdir(sysdir))) { + ret = process_device(&device, dent->d_name, -1, devices != NULL, flags); + if (ret) + continue; + + if (i >= MAX_DRM_NODES) { + fprintf(stderr, "More than %d drm nodes detected. " + "Please report a bug - that should not happen.\n" + "Skipping extra nodes\n", MAX_DRM_NODES); + break; + } + local_devices[i] = device; + i++; + } + node_count = i; + + drmFoldDuplicatedDevices(local_devices, node_count); + + device_count = 0; + for (i = 0; i < node_count; i++) { + if (!local_devices[i]) + continue; + + if ((devices != NULL) && (device_count < max_devices)) + devices[device_count] = local_devices[i]; + else + drmFreeDevice(&local_devices[i]); + + device_count++; + } + + closedir(sysdir); + + if (devices != NULL) + return MIN2(device_count, max_devices); + + return device_count; +} + +/** + * Get drm devices on the system + * + * \param devices the array of devices with drmDevicePtr elements + * can be NULL to get the device number first + * \param max_devices the maximum number of devices for the array + * + * \return on error - negative error code, + * if devices is NULL - total number of devices available on the system, + * alternatively the number of devices stored in devices[], which is + * capped by the max_devices. + */ +drm_public int drmGetDevices(drmDevicePtr devices[], int max_devices) +{ + return drmGetDevices2(DRM_DEVICE_GET_PCI_REVISION, devices, max_devices); +} + +drm_public char *drmGetDeviceNameFromFd2(int fd) +{ +#ifdef __linux__ + struct stat sbuf; + char path[PATH_MAX + 1], *value; + unsigned int maj, min; + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return NULL; + + snprintf(path, sizeof(path), "/sys/dev/char/%d:%d", maj, min); + + value = sysfs_uevent_get(path, "DEVNAME"); + if (!value) + return NULL; + + snprintf(path, sizeof(path), "/dev/%s", value); + free(value); + + return strdup(path); +#elif defined(__FreeBSD__) + return drmGetDeviceNameFromFd(fd); +#else + struct stat sbuf; + char node[PATH_MAX + 1]; + const char *dev_name; + int node_type; + int maj, min, n; + + if (fstat(fd, &sbuf)) + return NULL; + + maj = major(sbuf.st_rdev); + min = minor(sbuf.st_rdev); + + if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) + return NULL; + + node_type = drmGetMinorType(maj, min); + if (node_type == -1) + return NULL; + + dev_name = drmGetDeviceName(node_type); + if (!dev_name) + return NULL; + + n = snprintf(node, PATH_MAX, dev_name, DRM_DIR_NAME, min); + if (n == -1 || n >= PATH_MAX) + return NULL; + + return strdup(node); +#endif +} + +drm_public int drmSyncobjCreate(int fd, uint32_t flags, uint32_t *handle) +{ + struct drm_syncobj_create args; + int ret; + + memclear(args); + args.flags = flags; + args.handle = 0; + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_CREATE, &args); + if (ret) + return ret; + *handle = args.handle; + return 0; +} + +drm_public int drmSyncobjDestroy(int fd, uint32_t handle) +{ + struct drm_syncobj_destroy args; + + memclear(args); + args.handle = handle; + return drmIoctl(fd, DRM_IOCTL_SYNCOBJ_DESTROY, &args); +} + +drm_public int drmSyncobjHandleToFD(int fd, uint32_t handle, int *obj_fd) +{ + struct drm_syncobj_handle args; + int ret; + + memclear(args); + args.fd = -1; + args.handle = handle; + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &args); + if (ret) + return ret; + *obj_fd = args.fd; + return 0; +} + +drm_public int drmSyncobjFDToHandle(int fd, int obj_fd, uint32_t *handle) +{ + struct drm_syncobj_handle args; + int ret; + + memclear(args); + args.fd = obj_fd; + args.handle = 0; + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &args); + if (ret) + return ret; + *handle = args.handle; + return 0; +} + +drm_public int drmSyncobjImportSyncFile(int fd, uint32_t handle, + int sync_file_fd) +{ + struct drm_syncobj_handle args; + + memclear(args); + args.fd = sync_file_fd; + args.handle = handle; + args.flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE; + return drmIoctl(fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, &args); +} + +drm_public int drmSyncobjExportSyncFile(int fd, uint32_t handle, + int *sync_file_fd) +{ + struct drm_syncobj_handle args; + int ret; + + memclear(args); + args.fd = -1; + args.handle = handle; + args.flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE; + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, &args); + if (ret) + return ret; + *sync_file_fd = args.fd; + return 0; +} + +drm_public int drmSyncobjWait(int fd, uint32_t *handles, unsigned num_handles, + int64_t timeout_nsec, unsigned flags, + uint32_t *first_signaled) +{ + struct drm_syncobj_wait args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.timeout_nsec = timeout_nsec; + args.count_handles = num_handles; + args.flags = flags; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &args); + if (ret < 0) + return -errno; + + if (first_signaled) + *first_signaled = args.first_signaled; + return ret; +} + +drm_public int drmSyncobjReset(int fd, const uint32_t *handles, + uint32_t handle_count) +{ + struct drm_syncobj_array args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.count_handles = handle_count; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_RESET, &args); + return ret; +} + +drm_public int drmSyncobjSignal(int fd, const uint32_t *handles, + uint32_t handle_count) +{ + struct drm_syncobj_array args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.count_handles = handle_count; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_SIGNAL, &args); + return ret; +} + +drm_public int drmSyncobjTimelineSignal(int fd, const uint32_t *handles, + uint64_t *points, uint32_t handle_count) +{ + struct drm_syncobj_timeline_array args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.count_handles = handle_count; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args); + return ret; +} + +drm_public int drmSyncobjTimelineWait(int fd, uint32_t *handles, uint64_t *points, + unsigned num_handles, + int64_t timeout_nsec, unsigned flags, + uint32_t *first_signaled) +{ + struct drm_syncobj_timeline_wait args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.timeout_nsec = timeout_nsec; + args.count_handles = num_handles; + args.flags = flags; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, &args); + if (ret < 0) + return -errno; + + if (first_signaled) + *first_signaled = args.first_signaled; + return ret; +} + + +drm_public int drmSyncobjQuery(int fd, uint32_t *handles, uint64_t *points, + uint32_t handle_count) +{ + struct drm_syncobj_timeline_array args; + int ret; + + memclear(args); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.count_handles = handle_count; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &args); + if (ret) + return ret; + return 0; +} + +drm_public int drmSyncobjQuery2(int fd, uint32_t *handles, uint64_t *points, + uint32_t handle_count, uint32_t flags) +{ + struct drm_syncobj_timeline_array args; + + memclear(args); + args.handles = (uintptr_t)handles; + args.points = (uintptr_t)points; + args.count_handles = handle_count; + args.flags = flags; + + return drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &args); +} + + +drm_public int drmSyncobjTransfer(int fd, + uint32_t dst_handle, uint64_t dst_point, + uint32_t src_handle, uint64_t src_point, + uint32_t flags) +{ + struct drm_syncobj_transfer args; + int ret; + + memclear(args); + args.src_handle = src_handle; + args.dst_handle = dst_handle; + args.src_point = src_point; + args.dst_point = dst_point; + args.flags = flags; + + ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TRANSFER, &args); + + return ret; +} + +static char * +drmGetFormatModifierFromSimpleTokens(uint64_t modifier) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(drm_format_modifier_table); i++) { + if (drm_format_modifier_table[i].modifier == modifier) + return strdup(drm_format_modifier_table[i].modifier_name); + } + + return NULL; +} + +/** Retrieves a human-readable representation of a vendor (as a string) from + * the format token modifier + * + * \param modifier the format modifier token + * \return a char pointer to the human-readable form of the vendor. Caller is + * responsible for freeing it. + */ +drm_public char * +drmGetFormatModifierVendor(uint64_t modifier) +{ + unsigned int i; + uint8_t vendor = fourcc_mod_get_vendor(modifier); + + for (i = 0; i < ARRAY_SIZE(drm_format_modifier_vendor_table); i++) { + if (drm_format_modifier_vendor_table[i].vendor == vendor) + return strdup(drm_format_modifier_vendor_table[i].vendor_name); + } + + return NULL; +} + +/** Retrieves a human-readable representation string from a format token + * modifier + * + * If the dedicated function was not able to extract a valid name or searching + * the format modifier was not in the table, this function would return NULL. + * + * \param modifier the token format + * \return a malloc'ed string representation of the modifier. Caller is + * responsible for freeing the string returned. + * + */ +drm_public char * +drmGetFormatModifierName(uint64_t modifier) +{ + uint8_t vendorid = fourcc_mod_get_vendor(modifier); + char *modifier_found = NULL; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(modifier_format_vendor_table); i++) { + if (modifier_format_vendor_table[i].vendor == vendorid) + modifier_found = modifier_format_vendor_table[i].vendor_cb(modifier); + } + + if (!modifier_found) + return drmGetFormatModifierFromSimpleTokens(modifier); + + return modifier_found; +} + +/** + * Get a human-readable name for a DRM FourCC format. + * + * \param format The format. + * \return A malloc'ed string containing the format name. Caller is responsible + * for freeing it. + */ +drm_public char * +drmGetFormatName(uint32_t format) +{ + char *str, code[5]; + const char *be; + size_t str_size, i; + + be = (format & DRM_FORMAT_BIG_ENDIAN) ? "_BE" : ""; + format &= ~DRM_FORMAT_BIG_ENDIAN; + + if (format == DRM_FORMAT_INVALID) + return strdup("INVALID"); + + code[0] = (char) ((format >> 0) & 0xFF); + code[1] = (char) ((format >> 8) & 0xFF); + code[2] = (char) ((format >> 16) & 0xFF); + code[3] = (char) ((format >> 24) & 0xFF); + code[4] = '\0'; + + /* Trim spaces at the end */ + for (i = 3; i > 0 && code[i] == ' '; i--) + code[i] = '\0'; + + str_size = strlen(code) + strlen(be) + 1; + str = malloc(str_size); + if (!str) + return NULL; + + snprintf(str, str_size, "%s%s", code, be); + + return str; +} |