#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/dma-buf.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#ifdef __has_include
# if __has_include(<virtgpu_drm.h>)
# include <virtgpu_drm.h>
# define HAVE_VIRTGPU 1
# endif
#endif
#ifndef HAVE_VIRTGPU
# define HAVE_VIRTGPU 0
#endif
#include "drmtap_internal.h"
#include <drm_fourcc.h>
static int gpu_auto_process(drmtap_ctx *ctx, void *data,
drmtap_frame_info *frame, int force_egl);
static void drmtap_gem_close(drmtap_ctx *ctx, uint32_t handle) {
if (!ctx || handle == 0) {
return;
}
struct drm_gem_close gc;
memset(&gc, 0, sizeof(gc));
gc.handle = handle;
drmIoctl(ctx->drm_fd, DRM_IOCTL_GEM_CLOSE, &gc);
}
typedef struct {
int prime_fd;
int helper_drm_fd;
void *mapped;
size_t mapped_size;
uint32_t gem_handle;
int used_dumb_map;
int is_heap_buf;
int sync_started;
} frame_priv_t;
static void read_hdr_metadata_direct(drmtap_ctx *ctx, uint32_t crtc_id) {
ctx->cur_hdr_eotf = 0;
ctx->cur_hdr_max_nits = 0;
if (crtc_id == 0) {
return;
}
drmModeRes *res = drmModeGetResources(ctx->drm_fd);
if (!res) {
return;
}
uint32_t conn_id = 0;
for (int i = 0; i < res->count_connectors && conn_id == 0; i++) {
drmModeConnector *conn =
drmModeGetConnector(ctx->drm_fd, res->connectors[i]);
if (!conn) {
continue;
}
if (conn->connection == DRM_MODE_CONNECTED && conn->encoder_id) {
drmModeEncoder *enc =
drmModeGetEncoder(ctx->drm_fd, conn->encoder_id);
if (enc) {
if (enc->crtc_id == crtc_id) {
conn_id = conn->connector_id;
}
drmModeFreeEncoder(enc);
}
}
drmModeFreeConnector(conn);
}
drmModeFreeResources(res);
if (conn_id == 0) {
return;
}
drmModeObjectProperties *props = drmModeObjectGetProperties(
ctx->drm_fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
return;
}
for (uint32_t p = 0; p < props->count_props; p++) {
drmModePropertyRes *prop = drmModeGetProperty(ctx->drm_fd, props->props[p]);
if (!prop) {
continue;
}
if (strcmp(prop->name, "HDR_OUTPUT_METADATA") == 0 &&
props->prop_values[p] != 0) {
drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(
ctx->drm_fd, (uint32_t)props->prop_values[p]);
#if HAVE_HDR_METADATA
if (blob && blob->data &&
blob->length >= sizeof(struct hdr_output_metadata)) {
const struct hdr_output_metadata *m = blob->data;
const struct hdr_metadata_infoframe *inf =
&m->hdmi_metadata_type1;
ctx->cur_hdr_eotf = inf->eotf;
ctx->cur_hdr_max_nits = inf->max_cll
? inf->max_cll : inf->max_display_mastering_luminance;
}
#endif
if (blob) {
drmModeFreePropertyBlob(blob);
}
}
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
if (ctx->cur_hdr_eotf == DRMTAP_EOTF_PQ) {
drmtap_debug_log(ctx, "direct: HDR scanout eotf=%u peak=%u nits",
ctx->cur_hdr_eotf, ctx->cur_hdr_max_nits);
}
}
static uint32_t find_primary_plane(drmtap_ctx *ctx) {
drmModePlaneRes *planes = drmModeGetPlaneResources(ctx->drm_fd);
if (!planes) {
drmtap_debug_log(ctx, "drmModeGetPlaneResources failed: %s",
strerror(errno));
return 0;
}
uint32_t target_crtc = ctx->crtc_id;
uint32_t result = 0;
if (target_crtc == 0) {
drmModeRes *res = drmModeGetResources(ctx->drm_fd);
if (res) {
for (int i = 0; i < res->count_crtcs; i++) {
drmModeCrtc *crtc = drmModeGetCrtc(ctx->drm_fd, res->crtcs[i]);
if (crtc) {
if (crtc->mode_valid) {
target_crtc = crtc->crtc_id;
ctx->crtc_id = target_crtc;
drmtap_debug_log(ctx, "auto-selected CRTC %u", target_crtc);
drmModeFreeCrtc(crtc);
break;
}
drmModeFreeCrtc(crtc);
}
}
drmModeFreeResources(res);
}
}
if (target_crtc == 0) {
drmtap_debug_log(ctx, "no active CRTC found");
drmModeFreePlaneResources(planes);
return 0;
}
for (uint32_t i = 0; i < planes->count_planes; i++) {
drmModePlane *plane = drmModeGetPlane(ctx->drm_fd, planes->planes[i]);
if (!plane) {
continue;
}
if (plane->crtc_id != target_crtc) {
drmModeFreePlane(plane);
continue;
}
int is_primary = 0;
if (plane->fb_id != 0) {
drmModeObjectProperties *props = drmModeObjectGetProperties(
ctx->drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE);
if (props) {
for (uint32_t p = 0; p < props->count_props; p++) {
drmModePropertyRes *prop = drmModeGetProperty(
ctx->drm_fd, props->props[p]);
if (prop) {
if (strcmp(prop->name, "type") == 0 &&
props->prop_values[p] == DRM_PLANE_TYPE_PRIMARY) {
is_primary = 1;
}
drmModeFreeProperty(prop);
}
}
drmModeFreeObjectProperties(props);
}
if (is_primary || result == 0) {
result = plane->plane_id;
drmtap_debug_log(ctx, "find_primary_plane: matched plane=%u to crtc=%u (fb=%u, %s)",
plane->plane_id, target_crtc, plane->fb_id,
is_primary ? "PRIMARY" : "overlay");
}
}
drmModeFreePlane(plane);
if (result != 0 && is_primary) {
break;
}
}
drmModeFreePlaneResources(planes);
read_hdr_metadata_direct(ctx, ctx->crtc_id);
return result;
}
static int dmabuf_sync_start(int fd) {
struct dma_buf_sync sync = {
.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ
};
return ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
}
static int dmabuf_sync_end(int fd) {
struct dma_buf_sync sync = {
.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ
};
return ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
}
static int ensure_buf(void **buf, size_t *cap, size_t size) {
if (size == 0) {
return -EINVAL;
}
if (size > DRMTAP_MAX_FB_BYTES) {
return -EFBIG;
}
if (*buf && *cap >= size) {
return 0;
}
free(*buf);
*buf = malloc(size);
if (!*buf) {
*cap = 0;
return -ENOMEM;
}
*cap = size;
return 0;
}
static int is_virtio_gpu(drmtap_ctx *ctx) {
const char *driver = drmtap_gpu_driver(ctx);
return (driver && strcmp(driver, "virtio_gpu") == 0);
}
static int virtio_transfer_from_host(drmtap_ctx *ctx, uint32_t handle,
uint32_t width, uint32_t height) {
#if HAVE_VIRTGPU
struct drm_virtgpu_3d_transfer_from_host xfer = {0};
xfer.bo_handle = handle;
xfer.box.w = width;
xfer.box.h = height;
xfer.box.d = 1;
int ret = drmIoctl(ctx->drm_fd, DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST,
&xfer);
if (ret != 0) {
drmtap_debug_log(ctx, "TRANSFER_FROM_HOST failed: %s",
strerror(errno));
return -errno;
}
struct drm_virtgpu_3d_wait wait_args = {0};
wait_args.handle = handle;
ret = drmIoctl(ctx->drm_fd, DRM_IOCTL_VIRTGPU_WAIT, &wait_args);
if (ret != 0) {
drmtap_debug_log(ctx, "VIRTGPU_WAIT failed: %s", strerror(errno));
return -errno;
}
drmtap_debug_log(ctx, "virtio: transferred %ux%u from host",
width, height);
return 0;
#else
(void)ctx; (void)handle; (void)width; (void)height;
return -ENOTSUP;
#endif
}
static void *virtio_dumb_mmap(drmtap_ctx *ctx, uint32_t handle, size_t size) {
struct drm_mode_map_dumb map = {0};
map.handle = handle;
if (drmIoctl(ctx->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map) != 0) {
drmtap_debug_log(ctx, "MODE_MAP_DUMB failed: %s", strerror(errno));
return MAP_FAILED;
}
return mmap(NULL, size, PROT_READ, MAP_SHARED, ctx->drm_fd, map.offset);
}
static int validate_fb_size(uint32_t stride, uint32_t height) {
if (stride == 0 || height == 0) {
return -EINVAL;
}
if ((size_t)height > DRMTAP_MAX_FB_BYTES / stride) {
return -EFBIG;
}
return 0;
}
static int do_grab(drmtap_ctx *ctx, drmtap_frame_info *frame, int do_mmap) {
int ret;
frame_priv_t *priv = NULL;
drmModeFB2 *fb2 = NULL;
int prime_fd = -1;
uint32_t plane_id = find_primary_plane(ctx);
if (plane_id == 0) {
drmtap_set_error(ctx, "No active plane found for capture");
return -ENODEV;
}
drmModePlane *plane = drmModeGetPlane(ctx->drm_fd, plane_id);
if (!plane || plane->fb_id == 0) {
drmtap_set_error(ctx, "Plane %u has no framebuffer", plane_id);
if (plane) {
drmModeFreePlane(plane);
}
return -ENODEV;
}
uint32_t fb_id = plane->fb_id;
drmModeFreePlane(plane);
fb2 = drmModeGetFB2(ctx->drm_fd, fb_id);
if (!fb2) {
ret = -errno;
drmtap_set_error(ctx, "drmModeGetFB2(%u) failed: %s",
fb_id, strerror(errno));
return ret;
}
drmtap_debug_log(ctx, "FB2: %ux%u fmt=%.4s modifier=0x%lx",
fb2->width, fb2->height,
(const char *)&fb2->pixel_format,
(unsigned long)fb2->modifier);
ret = validate_fb_size(fb2->pitches[0], fb2->height);
if (ret != 0) {
drmtap_set_error(ctx, "rejecting framebuffer geometry %ux%u stride=%u",
fb2->width, fb2->height, fb2->pitches[0]);
drmModeFreeFB2(fb2);
return ret;
}
ctx->fb2_num_planes = 0;
for (int p = 0; p < 4; p++) {
ctx->fb2_pitches[p] = fb2->pitches[p];
ctx->fb2_offsets[p] = fb2->offsets[p];
if (fb2->handles[p] || fb2->pitches[p]) {
ctx->fb2_num_planes = p + 1;
}
}
int needs_helper = 0;
if (fb2->handles[0] == 0) {
needs_helper = 1;
} else {
ret = drmPrimeHandleToFD(ctx->drm_fd, fb2->handles[0],
O_RDONLY | O_CLOEXEC, &prime_fd);
if (ret < 0 && (errno == EACCES || errno == EPERM)) {
needs_helper = 1;
} else if (ret < 0) {
ret = -errno;
drmtap_set_error(ctx, "drmPrimeHandleToFD failed: %s", strerror(errno));
goto cleanup;
}
}
if (needs_helper) {
drmtap_debug_log(ctx,
"No CAP_SYS_ADMIN (needs helper), trying helper...");
size_t buf_size = (size_t)fb2->pitches[0] * fb2->height;
ret = ensure_buf(&ctx->pixel_buf, &ctx->pixel_buf_size, buf_size);
if (ret != 0) {
if (ret == -EFBIG) {
drmtap_set_error(ctx,
"framebuffer too large: %zu bytes (max %zu)",
buf_size, (size_t)DRMTAP_MAX_FB_BYTES);
}
drmModeFreeFB2(fb2);
return ret;
}
void *pixel_buf = ctx->pixel_buf;
helper_grab_result_t hresult;
ret = drmtap_helper_grab(ctx, &hresult, pixel_buf, buf_size);
if (ret < 0) {
drmtap_set_error(ctx,
"No CAP_SYS_ADMIN and helper failed (ret=%d). Install the "
"helper, restricting it first so the capability is not "
"world-usable: sudo cp drmtap-helper /usr/local/bin/ && "
"sudo chown root:<capture-group> /usr/local/bin/drmtap-helper && "
"sudo chmod 0750 /usr/local/bin/drmtap-helper && "
"sudo setcap cap_sys_admin+ep /usr/local/bin/drmtap-helper "
"(see SECURITY.md)",
ret);
drmModeFreeFB2(fb2);
return -EACCES;
}
priv = calloc(1, sizeof(frame_priv_t));
if (!priv) {
if (hresult.dmabuf_fd >= 0) {
close(hresult.dmabuf_fd);
}
drmModeFreeFB2(fb2);
return -ENOMEM;
}
priv->helper_drm_fd = -1;
priv->prime_fd = -1;
priv->gem_handle = 0;
priv->mapped = MAP_FAILED;
priv->mapped_size = 0;
priv->used_dumb_map = 1;
priv->is_heap_buf = 0;
memset(frame, 0, sizeof(*frame));
frame->width = hresult.wire.width;
frame->height = hresult.wire.height;
frame->stride = hresult.wire.stride;
frame->format = hresult.wire.format;
frame->modifier = hresult.wire.modifier;
frame->fb_id = hresult.wire.fb_id;
ctx->cur_hdr_eotf = hresult.wire.hdr_eotf;
ctx->cur_hdr_max_nits = hresult.wire.hdr_max_nits;
ret = validate_fb_size(frame->stride, frame->height);
if (ret != 0) {
drmtap_set_error(ctx, "helper sent invalid geometry %ux%u stride=%u",
frame->width, frame->height, frame->stride);
if (hresult.dmabuf_fd >= 0) {
close(hresult.dmabuf_fd);
}
free(priv);
drmModeFreeFB2(fb2);
return ret;
}
if (hresult.dmabuf_fd >= 0) {
int dmabuf_fd = hresult.dmabuf_fd;
int is_virgl = (hresult.wire.flags & HELPER_FLAG_VIRGL) != 0;
int pret = 0;
size_t mmap_size = (size_t)frame->stride * frame->height;
void *mapped = mmap(NULL, mmap_size, PROT_READ, MAP_SHARED,
dmabuf_fd, 0);
priv->prime_fd = dmabuf_fd;
if (mapped != MAP_FAILED) {
priv->mapped = mapped;
priv->mapped_size = mmap_size;
frame->data = mapped;
frame->dma_buf_fd = dmabuf_fd;
frame->_priv = priv;
priv->used_dumb_map = 0;
priv->sync_started = (dmabuf_sync_start(dmabuf_fd) == 0);
drmtap_debug_log(ctx,
"helper V3: mmap'd DMA-BUF fd=%d (%zu bytes), "
"EGL deswizzle available",
dmabuf_fd, mmap_size);
if (do_mmap) {
pret = gpu_auto_process(ctx, mapped, frame, is_virgl);
}
} else {
priv->mapped = MAP_FAILED;
priv->mapped_size = 0;
frame->data = NULL;
frame->dma_buf_fd = dmabuf_fd;
frame->_priv = priv;
drmtap_debug_log(ctx,
"helper V3: mmap failed but DMA-BUF fd=%d available "
"for EGL zero-copy", dmabuf_fd);
if (do_mmap) {
pret = gpu_auto_process(ctx, NULL, frame, is_virgl);
}
}
drmModeFreeFB2(fb2);
if (is_virgl && pret != 0) {
drmtap_frame_release(ctx, frame);
return pret;
}
return 0;
}
if (hresult.wire.data_size != (size_t)frame->stride * frame->height) {
drmtap_set_error(ctx, "helper V2 payload %u != expected %zu bytes",
hresult.wire.data_size,
(size_t)frame->stride * frame->height);
free(priv);
drmModeFreeFB2(fb2);
return -EPROTO;
}
frame->dma_buf_fd = -1;
frame->data = pixel_buf;
frame->_priv = priv;
if (do_mmap) {
gpu_auto_process(ctx, pixel_buf, frame, 0);
}
drmModeFreeFB2(fb2);
return 0;
}
priv = calloc(1, sizeof(frame_priv_t));
if (!priv) {
ret = -ENOMEM;
goto cleanup;
}
priv->prime_fd = prime_fd;
priv->helper_drm_fd = -1;
priv->gem_handle = fb2->handles[0];
priv->mapped = MAP_FAILED;
priv->mapped_size = 0;
memset(frame, 0, sizeof(*frame));
frame->width = fb2->width;
frame->height = fb2->height;
frame->stride = fb2->pitches[0];
frame->format = fb2->pixel_format;
frame->modifier = fb2->modifier;
frame->fb_id = fb_id;
frame->dma_buf_fd = prime_fd;
frame->data = NULL;
frame->_priv = priv;
if (do_mmap) {
size_t size = (size_t)fb2->pitches[0] * fb2->height;
void *mapped = MAP_FAILED;
if (is_virtio_gpu(ctx)) {
ret = virtio_transfer_from_host(ctx, fb2->handles[0],
fb2->width, fb2->height);
if (ret < 0) {
drmtap_debug_log(ctx,
"virtio transfer failed, trying standard mmap");
} else {
mapped = virtio_dumb_mmap(ctx, fb2->handles[0], size);
if (mapped != MAP_FAILED) {
priv->used_dumb_map = 1;
drmtap_debug_log(ctx,
"virtio: dumb-mapped %zu bytes at %p", size, mapped);
}
}
}
if (mapped == MAP_FAILED) {
dmabuf_sync_start(prime_fd);
mapped = mmap(NULL, size, PROT_READ, MAP_SHARED,
prime_fd, fb2->offsets[0]);
}
if (mapped == MAP_FAILED) {
int mmap_errno = errno;
drmtap_debug_log(ctx,
"mmap failed (%zu bytes: %s), trying EGL detile via DMA-BUF fd",
size, strerror(mmap_errno));
frame->data = NULL;
int rc = gpu_auto_process(ctx, NULL, frame, 0);
if (!frame->data) {
if (rc == 0) {
drmtap_set_error(ctx, "mmap failed (%zu bytes): %s",
size, strerror(mmap_errno));
rc = mmap_errno ? -mmap_errno : -EIO;
}
ret = rc;
goto cleanup;
}
} else {
priv->sync_started = (dmabuf_sync_start(prime_fd) == 0);
priv->mapped = mapped;
priv->mapped_size = size;
frame->data = mapped;
drmtap_debug_log(ctx, "mapped %zu bytes at %p", size, mapped);
gpu_auto_process(ctx, mapped, frame, 0);
}
}
drmModeFreeFB2(fb2);
return 0;
cleanup:
if (prime_fd >= 0) {
close(prime_fd);
}
if (fb2) {
drmModeFreeFB2(fb2);
}
if (priv) {
drmtap_gem_close(ctx, priv->gem_handle);
}
free(priv);
return ret;
}
#define DRMTAP_FMT_XR30 0x30335258u
#define DRMTAP_FMT_AR30 0x30335241u
#define DRMTAP_FMT_XR48 0x38345258u
#define DRMTAP_FMT_AR48 0x38345241u
#define DRMTAP_FMT_XB48 0x38344258u
#define DRMTAP_FMT_AB48 0x38344241u
#define DRMTAP_FMT_XR24 0x34325258u
static int reduce_linear_to_xrgb8888(drmtap_ctx *ctx, void *data,
drmtap_frame_info *frame) {
uint32_t fmt = frame->format;
int is_ar30 = (fmt == DRMTAP_FMT_XR30 || fmt == DRMTAP_FMT_AR30);
int is_rgb16 = (fmt == DRMTAP_FMT_XR48 || fmt == DRMTAP_FMT_AR48 ||
fmt == DRMTAP_FMT_XB48 || fmt == DRMTAP_FMT_AB48);
if (!is_ar30 && !is_rgb16) {
return 0;
}
size_t out_size = (size_t)frame->width * frame->height * 4u;
int b = ensure_buf(&ctx->deswizzle_buf, &ctx->deswizzle_buf_size, out_size);
if (b != 0) return b;
uint32_t dst_stride = frame->width * 4u;
int hdr = (ctx->cur_hdr_eotf == DRMTAP_EOTF_PQ);
int ret;
if (is_ar30) {
if (hdr) {
ret = drmtap_tonemap_hdr10(data, ctx->deswizzle_buf,
frame->width, frame->height, frame->stride, dst_stride,
fmt, ctx->cur_hdr_max_nits);
} else {
ret = drmtap_convert_format(data, ctx->deswizzle_buf,
frame->width, frame->height, frame->stride, dst_stride,
fmt, DRMTAP_FMT_XR24);
}
} else {
int bgr = (fmt == DRMTAP_FMT_XB48 || fmt == DRMTAP_FMT_AB48);
ret = drmtap_convert_rgb16(data, ctx->deswizzle_buf,
frame->width, frame->height, frame->stride, dst_stride,
bgr, ctx->cur_hdr_eotf, ctx->cur_hdr_max_nits);
}
if (ret != 0) return ret;
drmtap_debug_log(ctx, "auto-process: linear %s -> XRGB8888 (%s)",
is_ar30 ? "10-bit" : "16-bit",
hdr ? "HDR tone-mapped" : "SDR");
frame->data = ctx->deswizzle_buf;
frame->format = DRMTAP_FMT_XR24;
frame->stride = dst_stride;
return 1;
}
static int gpu_auto_process(drmtap_ctx *ctx, void *data,
drmtap_frame_info *frame, int force_egl) {
if (!data && !force_egl && frame->dma_buf_fd < 0) return 0;
uint64_t modifier = frame->modifier;
int is_linear = (modifier == DRM_FORMAT_MOD_LINEAR || modifier == 0);
if (data && is_linear && !force_egl) {
int red = reduce_linear_to_xrgb8888(ctx, data, frame);
if (red < 0) return red;
if (red == 1) return 0;
}
if (is_linear && !force_egl) {
return 0;
}
size_t size = (size_t)frame->stride * frame->height;
int bres = ensure_buf(&ctx->deswizzle_buf, &ctx->deswizzle_buf_size, size);
if (bres != 0) {
if (bres == -EFBIG) {
drmtap_set_error(ctx,
"framebuffer too large for deswizzle: %zu bytes (max %zu)",
size, (size_t)DRMTAP_MAX_FB_BYTES);
}
return bres;
}
#ifdef HAVE_EGL
drmtap_debug_log(ctx, "EGL check: fd=%d avail=%d mod=0x%lx",
frame->dma_buf_fd, drmtap_gpu_egl_available(ctx),
(unsigned long)modifier);
if (frame->dma_buf_fd >= 0 && drmtap_gpu_egl_available(ctx)) {
void *egl_data = NULL;
size_t egl_size = 0;
drmtap_debug_log(ctx, "auto-process: EGL convert (mod=0x%lx)",
(unsigned long)modifier);
int ret = drmtap_gpu_egl_convert(ctx, frame->dma_buf_fd,
frame->width, frame->height,
frame->stride, frame->format,
modifier, &egl_data, &egl_size);
drmtap_debug_log(ctx, "EGL convert: ret=%d data=%p", ret, egl_data);
if (ret == 0 && egl_data) {
if (egl_size > DRMTAP_MAX_FB_BYTES) {
free(egl_data);
drmtap_set_error(ctx, "EGL output too large: %zu bytes (max %zu)",
egl_size, (size_t)DRMTAP_MAX_FB_BYTES);
return -EFBIG;
}
free(ctx->deswizzle_buf);
ctx->deswizzle_buf = egl_data;
ctx->deswizzle_buf_size = egl_size;
frame->data = ctx->deswizzle_buf;
frame->format = DRM_FORMAT_XRGB8888;
frame->stride = frame->width * 4;
drmtap_debug_log(ctx, "auto-process: EGL detiled to linear XRGB8888");
return 0;
}
drmtap_debug_log(ctx, "auto-process: EGL failed (%d), trying CPU", ret);
}
#endif
if (force_egl) {
drmtap_set_error(ctx,
"virgl scanout needs GPU EGL readback, which is unavailable or failed");
return -ENOTSUP;
}
if (!data) {
drmtap_set_error(ctx,
"tiled scanout has no CPU mapping and EGL detile is unavailable");
return -ENOTSUP;
}
const char *driver = ctx->driver_name;
if (drmtap_gpu_intel_match(driver) ||
drmtap_gpu_nvidia_match(driver) ||
drmtap_gpu_amd_match(driver)) {
drmtap_debug_log(ctx, "auto-process: %s CPU deswizzle (mod=0x%lx)",
driver, (unsigned long)modifier);
int ret = drmtap_deswizzle(data, ctx->deswizzle_buf,
frame->width, frame->height,
frame->stride, frame->stride, modifier,
(size_t)frame->stride * frame->height);
if (ret == -ENOTSUP) {
drmtap_debug_log(ctx,
"auto-process: CCS modifier 0x%lx needs GPU deswizzle "
"(EGL/DMA-BUF), CPU deswizzle not possible — "
"returning raw pixels",
(unsigned long)modifier);
frame->modifier = 0;
return 0;
}
if (ret == 0) {
frame->data = ctx->deswizzle_buf;
drmtap_debug_log(ctx, "auto-process: CPU deswizzled to linear");
if (frame->format == DRMTAP_FMT_XR30 ||
frame->format == DRMTAP_FMT_AR30) {
int conv;
if (ctx->cur_hdr_eotf == DRMTAP_EOTF_PQ) {
drmtap_debug_log(ctx,
"auto-process: HDR10 AR30 -> tone-map to SDR (peak=%u)",
ctx->cur_hdr_max_nits);
conv = drmtap_tonemap_hdr10(
ctx->deswizzle_buf, ctx->deswizzle_buf,
frame->width, frame->height,
frame->stride, frame->stride,
frame->format, ctx->cur_hdr_max_nits);
} else {
drmtap_debug_log(ctx,
"auto-process: SDR 10-bit AR30 -> 8-bit XRGB8888");
conv = drmtap_convert_format(
ctx->deswizzle_buf, ctx->deswizzle_buf,
frame->width, frame->height,
frame->stride, frame->stride,
frame->format, DRMTAP_FMT_XR24);
}
if (conv != 0) {
return conv;
}
frame->format = DRMTAP_FMT_XR24;
}
}
return ret;
}
drmtap_debug_log(ctx, "auto-process: unknown driver '%s' mod=0x%lx",
driver, (unsigned long)modifier);
return 0;
}
int drmtap_grab(drmtap_ctx *ctx, drmtap_frame_info *frame) {
if (!ctx || !frame) {
return -EINVAL;
}
return do_grab(ctx, frame, 0);
}
int drmtap_grab_mapped(drmtap_ctx *ctx, drmtap_frame_info *frame) {
if (!ctx || !frame) {
return -EINVAL;
}
return do_grab(ctx, frame, 1);
}
void drmtap_frame_release(drmtap_ctx *ctx, drmtap_frame_info *frame) {
if (!frame) {
return;
}
frame_priv_t *priv = (frame_priv_t *)frame->_priv;
if (priv) {
if (priv->mapped && priv->mapped_size > 0) {
if (priv->is_heap_buf) {
free(priv->mapped);
} else if (priv->mapped != MAP_FAILED) {
if (priv->sync_started && priv->prime_fd >= 0) {
dmabuf_sync_end(priv->prime_fd);
}
munmap(priv->mapped, priv->mapped_size);
}
}
if (priv->prime_fd >= 0) {
close(priv->prime_fd);
}
if (priv->helper_drm_fd >= 0) {
close(priv->helper_drm_fd);
}
drmtap_gem_close(ctx, priv->gem_handle);
free(priv);
}
memset(frame, 0, sizeof(*frame));
frame->dma_buf_fd = -1;
}
void drmtap_fast_cleanup(drmtap_ctx *ctx) {
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
if (ctx->fast_slots[i].mmap_ptr &&
ctx->fast_slots[i].mmap_ptr != MAP_FAILED) {
munmap(ctx->fast_slots[i].mmap_ptr, ctx->fast_slots[i].mmap_size);
}
if (ctx->fast_slots[i].prime_fd >= 0) {
close(ctx->fast_slots[i].prime_fd);
}
drmtap_gem_close(ctx, ctx->fast_slots[i].gem_handle);
memset(&ctx->fast_slots[i], 0, sizeof(ctx->fast_slots[i]));
ctx->fast_slots[i].prime_fd = -1;
}
ctx->fast_plane_id = 0;
ctx->fast_last_fb_id = 0;
ctx->fast_initialized = 0;
}
static int find_or_alloc_slot(drmtap_ctx *ctx, uint32_t fb_id) {
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
if (ctx->fast_slots[i].fb_id == fb_id) {
return i;
}
}
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
if (ctx->fast_slots[i].fb_id == 0) {
return i;
}
}
if (ctx->fast_slots[0].mmap_ptr &&
ctx->fast_slots[0].mmap_ptr != MAP_FAILED) {
munmap(ctx->fast_slots[0].mmap_ptr, ctx->fast_slots[0].mmap_size);
}
if (ctx->fast_slots[0].prime_fd >= 0) {
close(ctx->fast_slots[0].prime_fd);
}
drmtap_gem_close(ctx, ctx->fast_slots[0].gem_handle);
memset(&ctx->fast_slots[0], 0, sizeof(ctx->fast_slots[0]));
ctx->fast_slots[0].prime_fd = -1;
return 0;
}
int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame) {
if (!ctx || !frame) {
return -EINVAL;
}
int ret;
if (!ctx->fast_initialized) {
ctx->fast_plane_id = find_primary_plane(ctx);
if (ctx->fast_plane_id == 0) {
drmtap_set_error(ctx, "No active plane found for capture");
return -ENODEV;
}
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
memset(&ctx->fast_slots[i], 0, sizeof(ctx->fast_slots[i]));
ctx->fast_slots[i].prime_fd = -1;
}
ctx->fast_last_fb_id = 0;
ctx->fast_initialized = 1;
drmtap_debug_log(ctx, "fast2: initialized plane=%u", ctx->fast_plane_id);
}
drmModePlane *plane = drmModeGetPlane(ctx->drm_fd, ctx->fast_plane_id);
if (!plane || plane->fb_id == 0) {
if (plane) drmModeFreePlane(plane);
drmtap_fast_cleanup(ctx);
return -ENODEV;
}
uint32_t fb_id = plane->fb_id;
drmModeFreePlane(plane);
if (fb_id == ctx->fast_last_fb_id) {
for (int i = 0; i < DRMTAP_FAST_SLOTS; i++) {
if (ctx->fast_slots[i].fb_id == fb_id && ctx->fast_slots[i].mmap_ptr) {
if (is_virtio_gpu(ctx)) {
ret = virtio_transfer_from_host(ctx,
ctx->fast_slots[i].gem_handle,
ctx->fast_slots[i].width,
ctx->fast_slots[i].height);
if (ret < 0) return ret;
} else {
dmabuf_sync_start(ctx->fast_slots[i].prime_fd);
}
frame->data = ctx->fast_slots[i].mmap_ptr;
frame->dma_buf_fd = ctx->fast_slots[i].prime_fd;
frame->width = ctx->fast_slots[i].width;
frame->height = ctx->fast_slots[i].height;
frame->stride = ctx->fast_slots[i].stride;
frame->format = ctx->fast_slots[i].format;
frame->modifier = ctx->fast_slots[i].modifier;
frame->fb_id = fb_id;
frame->_priv = NULL;
int pr = gpu_auto_process(ctx, frame->data, frame, 0);
if (pr != 0) {
return pr;
}
return 0;
}
}
}
int slot = find_or_alloc_slot(ctx, fb_id);
if (ctx->fast_slots[slot].fb_id == fb_id && ctx->fast_slots[slot].mmap_ptr) {
drmtap_debug_log(ctx, "fast2: CACHE HIT fb=%u slot=%d gem=%u",
fb_id, slot, ctx->fast_slots[slot].gem_handle);
if (is_virtio_gpu(ctx)) {
ret = virtio_transfer_from_host(ctx, ctx->fast_slots[slot].gem_handle,
ctx->fast_slots[slot].width,
ctx->fast_slots[slot].height);
if (ret < 0) {
drmtap_debug_log(ctx, "fast2: cached transfer failed: %d", ret);
return ret;
}
} else {
dmabuf_sync_start(ctx->fast_slots[slot].prime_fd);
}
ctx->fast_last_fb_id = fb_id;
frame->data = ctx->fast_slots[slot].mmap_ptr;
frame->dma_buf_fd = ctx->fast_slots[slot].prime_fd;
frame->width = ctx->fast_slots[slot].width;
frame->height = ctx->fast_slots[slot].height;
frame->stride = ctx->fast_slots[slot].stride;
frame->format = ctx->fast_slots[slot].format;
frame->modifier = ctx->fast_slots[slot].modifier;
frame->fb_id = fb_id;
frame->_priv = NULL;
gpu_auto_process(ctx, frame->data, frame, 0);
return 0;
}
drmtap_debug_log(ctx, "fast2: CACHE MISS fb=%u slot=%d (cold start)",
fb_id, slot);
drmModeFB2 *fb2 = drmModeGetFB2(ctx->drm_fd, fb_id);
if (!fb2) {
ret = -errno;
drmtap_set_error(ctx, "fast2: drmModeGetFB2(%u) failed: %s",
fb_id, strerror(errno));
return ret;
}
if (fb2->handles[0] == 0) {
drmModeFreeFB2(fb2);
drmtap_set_error(ctx, "fast2: handles[0]==0, no CAP_SYS_ADMIN");
return -EACCES;
}
ret = validate_fb_size(fb2->pitches[0], fb2->height);
if (ret != 0) {
drmtap_set_error(ctx, "fast2: rejecting geometry %ux%u stride=%u",
fb2->width, fb2->height, fb2->pitches[0]);
drmtap_gem_close(ctx, fb2->handles[0]);
drmModeFreeFB2(fb2);
return ret;
}
int prime_fd = -1;
ret = drmPrimeHandleToFD(ctx->drm_fd, fb2->handles[0],
O_RDONLY | O_CLOEXEC, &prime_fd);
if (ret < 0) {
ret = -errno;
drmtap_gem_close(ctx, fb2->handles[0]);
drmModeFreeFB2(fb2);
return ret;
}
size_t size = (size_t)fb2->pitches[0] * fb2->height;
void *mapped = MAP_FAILED;
if (is_virtio_gpu(ctx)) {
ret = virtio_transfer_from_host(ctx, fb2->handles[0],
fb2->width, fb2->height);
if (ret >= 0) {
mapped = virtio_dumb_mmap(ctx, fb2->handles[0], size);
}
}
if (mapped == MAP_FAILED) {
dmabuf_sync_start(prime_fd);
mapped = mmap(NULL, size, PROT_READ, MAP_SHARED,
prime_fd, fb2->offsets[0]);
}
if (mapped == MAP_FAILED) {
close(prime_fd);
drmtap_gem_close(ctx, fb2->handles[0]);
drmModeFreeFB2(fb2);
return -ENOMEM;
}
ctx->fast_slots[slot].fb_id = fb_id;
ctx->fast_slots[slot].gem_handle = fb2->handles[0];
ctx->fast_slots[slot].prime_fd = prime_fd;
ctx->fast_slots[slot].mmap_ptr = mapped;
ctx->fast_slots[slot].mmap_size = size;
ctx->fast_slots[slot].width = fb2->width;
ctx->fast_slots[slot].height = fb2->height;
ctx->fast_slots[slot].stride = fb2->pitches[0];
ctx->fast_slots[slot].format = fb2->pixel_format;
ctx->fast_slots[slot].modifier = fb2->modifier;
drmtap_debug_log(ctx, "fast2: cached fb=%u slot=%d gem=%u %ux%u",
fb_id, slot, fb2->handles[0], fb2->width, fb2->height);
drmModeFreeFB2(fb2);
ctx->fast_last_fb_id = fb_id;
frame->data = mapped;
frame->dma_buf_fd = prime_fd;
frame->width = ctx->fast_slots[slot].width;
frame->height = ctx->fast_slots[slot].height;
frame->stride = ctx->fast_slots[slot].stride;
frame->format = ctx->fast_slots[slot].format;
frame->modifier = ctx->fast_slots[slot].modifier;
frame->fb_id = fb_id;
frame->_priv = NULL;
gpu_auto_process(ctx, frame->data, frame, 0);
return 0;
}