#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "drmtap_internal.h"
static _Thread_local char g_static_error[512] = "";
void drmtap_set_error(drmtap_ctx *ctx, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
if (ctx) {
vsnprintf(ctx->error_msg, sizeof(ctx->error_msg), fmt, ap);
} else {
vsnprintf(g_static_error, sizeof(g_static_error), fmt, ap);
}
va_end(ap);
}
void drmtap_debug_log(drmtap_ctx *ctx, const char *fmt, ...) {
if (!ctx || !ctx->debug) {
return;
}
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[drmtap] ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
static int open_drm_auto(drmtap_ctx *ctx) {
char path[64];
const char *env_dev = getenv("DRM_DEVICE");
if (env_dev) {
drmtap_debug_log(ctx, "trying DRM_DEVICE=%s", env_dev);
int fd = open(env_dev, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
snprintf(ctx->device_path, sizeof(ctx->device_path), "%s", env_dev);
return fd;
}
drmtap_debug_log(ctx, "DRM_DEVICE open failed: %s", strerror(errno));
}
int fallback_fd = -1;
char fallback_path[64] = {0};
for (int i = 0; i < 16; i++) {
snprintf(path, sizeof(path), "/dev/dri/card%d", i);
int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
drmtap_debug_log(ctx, "probing %s", path);
drmModeRes *res = drmModeGetResources(fd);
if (!res) {
drmtap_debug_log(ctx, " no KMS resources, skipping");
close(fd);
continue;
}
int has_active = 0;
for (int j = 0; j < res->count_crtcs; j++) {
drmModeCrtc *crtc = drmModeGetCrtc(fd, res->crtcs[j]);
if (crtc) {
if (crtc->mode_valid && crtc->buffer_id > 0) {
drmtap_debug_log(ctx, " CRTC %u active (%dx%d)",
crtc->crtc_id, crtc->width, crtc->height);
has_active = 1;
}
drmModeFreeCrtc(crtc);
if (has_active) break;
}
}
drmModeFreeResources(res);
if (has_active) {
if (fallback_fd >= 0) close(fallback_fd);
snprintf(ctx->device_path, sizeof(ctx->device_path), "%s", path);
return fd;
}
if (fallback_fd < 0) {
fallback_fd = fd;
snprintf(fallback_path, sizeof(fallback_path), "%s", path);
drmtap_debug_log(ctx, " no active CRTC, saved as fallback");
} else {
close(fd);
}
}
if (fallback_fd >= 0) {
snprintf(ctx->device_path, sizeof(ctx->device_path), "%s", fallback_path);
drmtap_debug_log(ctx, "using fallback device %s (no active CRTC found)", fallback_path);
return fallback_fd;
}
return -1;
}
static void detect_driver(drmtap_ctx *ctx) {
drmVersion *ver = drmGetVersion(ctx->drm_fd);
if (ver) {
snprintf(ctx->driver_name, sizeof(ctx->driver_name), "%.*s",
ver->name_len, ver->name);
drmtap_debug_log(ctx, "GPU driver: %s (v%d.%d.%d)",
ctx->driver_name, ver->version_major,
ver->version_minor, ver->version_patchlevel);
drmFreeVersion(ver);
} else {
drmtap_debug_log(ctx, "warning: could not get DRM version");
}
}
int drmtap_version(void) {
return (DRMTAP_VERSION_MAJOR << 16) |
(DRMTAP_VERSION_MINOR << 8) |
DRMTAP_VERSION_PATCH;
}
drmtap_ctx *drmtap_open(const drmtap_config *config) {
drmtap_ctx *ctx = calloc(1, sizeof(drmtap_ctx));
if (!ctx) {
drmtap_set_error(NULL, "Failed to allocate context: %s",
strerror(errno));
return NULL;
}
ctx->drm_fd = -1;
ctx->helper_pid = -1;
ctx->helper_fd = -1;
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
ctx->fast_slots[i].prime_fd = -1;
}
if (config) {
ctx->debug = config->debug;
ctx->crtc_id = config->crtc_id;
if (config->device_path) {
snprintf(ctx->device_path, sizeof(ctx->device_path),
"%s", config->device_path);
}
if (config->helper_path) {
snprintf(ctx->helper_path, sizeof(ctx->helper_path),
"%s", config->helper_path);
}
}
const char *dbg_env = getenv("DRMTAP_DEBUG");
if (dbg_env && dbg_env[0] == '1') {
ctx->debug = 1;
}
drmtap_debug_log(ctx, "drmtap v%d.%d.%d opening (pid=%d uid=%d)",
DRMTAP_VERSION_MAJOR, DRMTAP_VERSION_MINOR,
DRMTAP_VERSION_PATCH, getpid(), getuid());
drmtap_debug_log(ctx, "device_path=[%s] DRM_DEVICE=[%s]", ctx->device_path, getenv("DRM_DEVICE") ? getenv("DRM_DEVICE") : "(null)");
if (ctx->device_path[0]) {
ctx->drm_fd = open(ctx->device_path, O_RDWR | O_CLOEXEC);
if (ctx->drm_fd < 0) {
drmtap_set_error(NULL, "Failed to open %s: %s",
ctx->device_path, strerror(errno));
drmtap_debug_log(ctx, "open(%s) FAILED: %s", ctx->device_path, strerror(errno));
goto fail;
}
} else {
ctx->drm_fd = open_drm_auto(ctx);
if (ctx->drm_fd < 0) {
drmtap_set_error(NULL, "No DRM device found — is a GPU attached?");
goto fail;
}
}
{
int high_fd = fcntl(ctx->drm_fd, F_DUPFD_CLOEXEC, 100);
if (high_fd >= 0) {
close(ctx->drm_fd);
ctx->drm_fd = high_fd;
}
}
detect_driver(ctx);
if (drmSetClientCap(ctx->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) < 0) {
drmtap_debug_log(ctx,
"warning: DRM_CLIENT_CAP_UNIVERSAL_PLANES not supported");
}
if (drmSetClientCap(ctx->drm_fd, DRM_CLIENT_CAP_ATOMIC, 1) < 0) {
drmtap_debug_log(ctx,
"warning: DRM_CLIENT_CAP_ATOMIC not supported "
"(connector CRTC_ID fallback unavailable)");
}
drmtap_debug_log(ctx, "context opened: %s (%s)",
ctx->device_path, ctx->driver_name);
return ctx;
fail:
if (ctx->drm_fd >= 0) {
close(ctx->drm_fd);
}
free(ctx);
return NULL;
}
void drmtap_close(drmtap_ctx *ctx) {
if (!ctx) {
return;
}
drmtap_debug_log(ctx, "closing context");
drmtap_fast_cleanup(ctx);
drmtap_gpu_egl_thread_cleanup();
drmtap_helper_stop(ctx);
if (ctx->drm_fd >= 0) {
close(ctx->drm_fd);
ctx->drm_fd = -1;
}
free(ctx->deswizzle_buf);
ctx->deswizzle_buf = NULL;
ctx->deswizzle_buf_size = 0;
free(ctx->pixel_buf);
ctx->pixel_buf = NULL;
ctx->pixel_buf_size = 0;
free(ctx);
}
const char *drmtap_error(drmtap_ctx *ctx) {
if (ctx) {
return ctx->error_msg[0] ? ctx->error_msg : NULL;
}
return g_static_error[0] ? g_static_error : NULL;
}
const char *drmtap_gpu_driver(drmtap_ctx *ctx) {
if (!ctx || !ctx->driver_name[0]) {
return NULL;
}
return ctx->driver_name;
}
int drmtap_drm_fd(drmtap_ctx *ctx) {
if (!ctx) {
return -1;
}
return ctx->drm_fd;
}