#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/un.h>
#include "wire.h"
#include <sys/prctl.h>
#include <limits.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif
#ifdef HAVE_SECCOMP
#include <seccomp.h>
#endif
#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/virtio_gpu.h>
#ifndef DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST
struct drm_virtgpu_3d_transfer_from_host {
__u32 bo_handle;
__u32 pad;
__u64 offset;
__u32 level;
__u32 stride;
__u32 layer_stride;
struct drm_virtgpu_3d_box {
__u32 x, y, z, w, h, d;
} box;
};
struct drm_virtgpu_3d_wait {
__u32 handle;
__u32 flags;
};
#define DRM_VIRTGPU_TRANSFER_FROM_HOST 0x07
#define DRM_VIRTGPU_WAIT 0x08
#define DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_TRANSFER_FROM_HOST, \
struct drm_virtgpu_3d_transfer_from_host)
#define DRM_IOCTL_VIRTGPU_WAIT \
DRM_IOW(DRM_COMMAND_BASE + DRM_VIRTGPU_WAIT, \
struct drm_virtgpu_3d_wait)
#endif
#ifndef DRM_IOCTL_VIRTGPU_GETPARAM
struct drm_virtgpu_getparam {
__u64 param;
__u64 value;
};
#define VIRTGPU_PARAM_3D_FEATURES 1
#define DRM_VIRTGPU_GETPARAM 0x03
#define DRM_IOCTL_VIRTGPU_GETPARAM \
DRM_IOWR(DRM_COMMAND_BASE + DRM_VIRTGPU_GETPARAM, \
struct drm_virtgpu_getparam)
#endif
#endif
#define HELPER_SOCKET_FD 3
#define CMD_GRAB 0x01
#define CMD_GET_CURSOR 0x02
#define CMD_QUIT 0xFF
struct helper_cmd_grab {
uint8_t cmd;
uint8_t _pad1[3];
uint32_t crtc_id;
};
#define RESP_OK 0x00
#define RESP_ERROR 0x01
#define FLAG_HAS_DMABUF 0x01
#define FLAG_VIRGL 0x02
struct grab_metadata {
uint32_t width;
uint32_t height;
uint32_t stride;
uint32_t format;
uint32_t fb_id;
uint32_t data_size;
uint64_t modifier;
uint32_t seq;
uint64_t timestamp_ms;
uint32_t flags;
uint32_t hdr_eotf;
uint32_t hdr_max_nits;
};
static int send_fd(int sock, int fd, const void *meta, size_t meta_len) {
return wire_send_fd(sock, fd, meta, meta_len);
}
#ifdef HAVE_LIBCAP
static int drop_caps(void) {
cap_t caps = cap_init();
if (!caps) {
perror("drmtap-helper: cap_init failed"); return -1;
}
cap_value_t keep[] = { CAP_SYS_ADMIN };
if (cap_set_flag(caps, CAP_PERMITTED, 1, keep, CAP_SET) != 0 ||
cap_set_flag(caps, CAP_EFFECTIVE, 1, keep, CAP_SET) != 0) {
int saved = errno;
cap_free(caps);
errno = saved;
perror("drmtap-helper: cap_set_flag failed"); return -1;
}
int ret = cap_set_proc(caps);
int saved = errno;
cap_free(caps);
if (ret != 0) {
errno = saved;
perror("drmtap-helper: cap_set_proc failed");
return -1;
}
fprintf(stderr, "drmtap-helper: dropped caps, keeping CAP_SYS_ADMIN\n");
return 0;
}
#endif
#ifdef HAVE_SECCOMP
static int install_seccomp(void) {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
if (!ctx) {
fprintf(stderr, "drmtap-helper: seccomp_init failed\n"); return -1;
}
int allowed[] = {
SCMP_SYS(read), SCMP_SYS(write), SCMP_SYS(close),
SCMP_SYS(ioctl),
SCMP_SYS(sendto),
SCMP_SYS(sendmsg),
SCMP_SYS(recvfrom),
SCMP_SYS(mmap), SCMP_SYS(munmap),
SCMP_SYS(brk),
SCMP_SYS(fstat), SCMP_SYS(newfstatat),
SCMP_SYS(fcntl),
SCMP_SYS(exit_group), SCMP_SYS(exit),
SCMP_SYS(rt_sigreturn),
SCMP_SYS(clock_gettime),
};
for (size_t i = 0; i < sizeof(allowed) / sizeof(allowed[0]); i++) {
int rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, allowed[i], 0);
if (rc != 0) {
seccomp_release(ctx);
fprintf(stderr, "drmtap-helper: seccomp_rule_add failed: %s\n",
strerror(-rc));
return -1;
}
}
int ret = seccomp_load(ctx);
seccomp_release(ctx);
if (ret != 0) {
fprintf(stderr, "drmtap-helper: seccomp_load failed: %s\n",
strerror(-ret));
return -1;
}
fprintf(stderr, "drmtap-helper: seccomp filter installed\n");
return 0;
}
#endif
static int send_all(int sock, const void *buf, size_t len) {
return wire_send_all(sock, buf, len);
}
static int recv_all(int sock, void *buf, size_t len) {
return wire_recv_all(sock, buf, len);
}
static int send_error(int sock, const char *reason) {
fprintf(stderr, "drmtap-helper: %s\n", reason);
struct grab_metadata meta = {0};
send_all(sock, &meta, sizeof(meta));
return -1;
}
static int send_error_errno(int sock, const char *reason) {
int saved = errno;
fprintf(stderr, "drmtap-helper: %s: %s\n", reason, strerror(saved));
struct grab_metadata meta = {0};
send_all(sock, &meta, sizeof(meta));
return -1;
}
struct cursor_metadata {
int32_t x, y;
int32_t hot_x, hot_y;
uint32_t width, height;
uint32_t visible;
uint32_t data_size;
};
static uint64_t get_prop_val(int drm_fd, uint32_t obj_id, uint32_t obj_type,
const char *name) {
uint64_t val = 0;
drmModeObjectProperties *props =
drmModeObjectGetProperties(drm_fd, obj_id, obj_type);
if (!props) return 0;
for (uint32_t i = 0; i < props->count_props; i++) {
drmModePropertyRes *prop = drmModeGetProperty(drm_fd, props->props[i]);
if (prop) {
if (strcmp(prop->name, name) == 0) {
val = props->prop_values[i];
drmModeFreeProperty(prop);
break;
}
drmModeFreeProperty(prop);
}
}
drmModeFreeObjectProperties(props);
return val;
}
static int cursor_and_send(int sock, int drm_fd, uint32_t target_crtc) {
struct cursor_metadata meta = {0};
drmModePlaneRes *planes = drmModeGetPlaneResources(drm_fd);
if (!planes) {
send_all(sock, &meta, sizeof(meta));
return 0;
}
uint32_t cursor_fb = 0, cursor_plane = 0;
for (uint32_t i = 0; i < planes->count_planes; i++) {
drmModePlane *plane = drmModeGetPlane(drm_fd, planes->planes[i]);
if (!plane) continue;
if (plane->fb_id != 0 &&
(target_crtc == 0 || plane->crtc_id == target_crtc)) {
if (get_prop_val(drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE,
"type") == DRM_PLANE_TYPE_CURSOR) {
cursor_fb = plane->fb_id;
cursor_plane = plane->plane_id;
drmModeFreePlane(plane);
break;
}
}
drmModeFreePlane(plane);
}
drmModeFreePlaneResources(planes);
if (cursor_fb == 0) {
send_all(sock, &meta, sizeof(meta));
return 0;
}
meta.x = (int32_t)get_prop_val(drm_fd, cursor_plane, DRM_MODE_OBJECT_PLANE, "CRTC_X");
meta.y = (int32_t)get_prop_val(drm_fd, cursor_plane, DRM_MODE_OBJECT_PLANE, "CRTC_Y");
meta.hot_x = (int32_t)get_prop_val(drm_fd, cursor_plane, DRM_MODE_OBJECT_PLANE, "HOTSPOT_X");
meta.hot_y = (int32_t)get_prop_val(drm_fd, cursor_plane, DRM_MODE_OBJECT_PLANE, "HOTSPOT_Y");
drmModeFB2 *fb2 = drmModeGetFB2(drm_fd, cursor_fb);
if (!fb2 || fb2->handles[0] == 0) {
if (fb2) drmModeFreeFB2(fb2);
send_all(sock, &meta, sizeof(meta));
return 0;
}
meta.width = fb2->width;
meta.height = fb2->height;
uint32_t cw = fb2->width, ch = fb2->height, cstride = fb2->pitches[0];
uint32_t chandle = fb2->handles[0];
drmModeFreeFB2(fb2);
if (!(cw <= 256 && ch <= 256 && cstride != 0 && cstride >= cw * 4)) {
send_all(sock, &meta, sizeof(meta));
return 0;
}
int prime_fd = -1;
void *mapped = MAP_FAILED;
size_t map_size = (size_t)cstride * ch;
if (drmPrimeHandleToFD(drm_fd, chandle, O_RDONLY | O_CLOEXEC,
&prime_fd) == 0 && prime_fd >= 0) {
mapped = mmap(NULL, map_size, PROT_READ, MAP_SHARED, prime_fd, 0);
}
if (mapped == MAP_FAILED) {
if (prime_fd >= 0) {
close(prime_fd);
}
send_all(sock, &meta, sizeof(meta));
return 0;
}
static uint8_t packed[256 * 256 * 4];
size_t tight = (size_t)cw * ch * 4;
for (uint32_t y = 0; y < ch; y++) {
memcpy(packed + (size_t)y * cw * 4,
(const uint8_t *)mapped + (size_t)y * cstride,
(size_t)cw * 4);
}
munmap(mapped, map_size);
close(prime_fd);
meta.visible = 1;
meta.data_size = (uint32_t)tight;
send_all(sock, &meta, sizeof(meta));
send_all(sock, packed, tight);
return 0;
}
static void read_hdr_metadata(int drm_fd, uint32_t crtc_id,
uint32_t *eotf, uint32_t *max_nits) {
*eotf = 0;
*max_nits = 0;
if (crtc_id == 0) {
return;
}
drmModeRes *res = drmModeGetResources(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(drm_fd, res->connectors[i]);
if (!conn) {
continue;
}
if (conn->connection == DRM_MODE_CONNECTED && conn->encoder_id) {
drmModeEncoder *enc = drmModeGetEncoder(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(
drm_fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
if (!props) {
return;
}
for (uint32_t p = 0; p < props->count_props; p++) {
drmModePropertyRes *prop = drmModeGetProperty(drm_fd, props->props[p]);
if (!prop) {
continue;
}
if (strcmp(prop->name, "HDR_OUTPUT_METADATA") == 0 &&
props->prop_values[p] != 0) {
drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(
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;
*eotf = inf->eotf;
*max_nits = inf->max_cll ? inf->max_cll
: inf->max_display_mastering_luminance;
}
#endif
if (blob) {
drmModeFreePropertyBlob(blob);
}
}
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
}
static int grab_and_send(int sock, int drm_fd, uint32_t target_crtc, int is_virtio) {
void *mapped = MAP_FAILED;
size_t mapped_size = 0;
drmModePlaneRes *planes = drmModeGetPlaneResources(drm_fd);
if (!planes) {
return send_error_errno(sock, "drmModeGetPlaneResources failed");
}
uint32_t fb_id = 0;
uint32_t matched_crtc = 0;
for (uint32_t i = 0; i < planes->count_planes; i++) {
drmModePlane *plane = drmModeGetPlane(drm_fd, planes->planes[i]);
if (!plane) continue;
if (target_crtc != 0) {
if (plane->crtc_id != target_crtc) {
drmModeFreePlane(plane);
continue;
}
}
if (plane->fb_id != 0) {
drmModeObjectProperties *props = drmModeObjectGetProperties(
drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE);
int is_primary = 0;
if (props) {
for (uint32_t p = 0; p < props->count_props; p++) {
drmModePropertyRes *prop = drmModeGetProperty(
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 || fb_id == 0) {
fb_id = plane->fb_id;
matched_crtc = plane->crtc_id;
fprintf(stderr, "drmtap-helper: matched plane=%u to crtc=%u (fb=%u, %s)\n",
plane->plane_id, target_crtc, fb_id, is_primary ? "PRIMARY" : "overlay");
if (is_primary) {
drmModeFreePlane(plane);
break;
}
}
}
drmModeFreePlane(plane);
}
drmModeFreePlaneResources(planes);
if (fb_id == 0) {
return send_error(sock, "no active framebuffer found");
}
drmModeFB2 *fb2 = drmModeGetFB2(drm_fd, fb_id);
if (!fb2) {
return send_error_errno(sock, "drmModeGetFB2 failed");
}
if (fb2->handles[0] == 0) {
drmModeFreeFB2(fb2);
return send_error(sock, "handles[0]==0 (CAP_SYS_ADMIN not set?)");
}
if (fb2->pitches[0] == 0 || fb2->height == 0 ||
(size_t)fb2->height > ((size_t)7680 * 4320 * 4) / fb2->pitches[0]) {
drmModeFreeFB2(fb2);
return send_error(sock, "rejecting framebuffer geometry (too large)");
}
uint32_t gem_handle = fb2->handles[0];
if (is_virtio) {
#ifdef __linux__
struct drm_virtgpu_3d_transfer_from_host xfer = {0};
xfer.bo_handle = gem_handle;
xfer.box.w = fb2->width;
xfer.box.h = fb2->height;
xfer.box.d = 1;
if (drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST, &xfer) == 0) {
struct drm_virtgpu_3d_wait wait_args = {0};
wait_args.handle = gem_handle;
drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_WAIT, &wait_args);
}
#endif
}
static int virtio_detected = 0;
static int virtio_virgl = 0;
if (is_virtio && !virtio_detected) {
virtio_detected = 1;
#if defined(__linux__) && defined(DRM_IOCTL_VIRTGPU_GETPARAM)
uint64_t features = 0;
struct drm_virtgpu_getparam gp = {
.param = VIRTGPU_PARAM_3D_FEATURES,
.value = (uint64_t)(uintptr_t)&features,
};
if (drmIoctl(drm_fd, DRM_IOCTL_VIRTGPU_GETPARAM, &gp) == 0 && features != 0) {
virtio_virgl = 1;
}
fprintf(stderr, "drmtap-helper: virtio 3D features=%llu -> %s path\n",
(unsigned long long)features,
virtio_virgl ? "DMA-BUF + GPU readback (virgl)"
: "zero-copy DMA-BUF");
#endif
}
mapped_size = (size_t)fb2->pitches[0] * fb2->height;
struct grab_metadata meta = {0};
meta.width = fb2->width;
meta.height = fb2->height;
meta.stride = fb2->pitches[0];
meta.format = fb2->pixel_format;
meta.fb_id = fb_id;
meta.modifier = fb2->modifier;
read_hdr_metadata(drm_fd, matched_crtc, &meta.hdr_eotf, &meta.hdr_max_nits);
if (meta.hdr_eotf == 2 || meta.hdr_eotf == 3) {
fprintf(stderr, "drmtap-helper: HDR scanout eotf=%u peak=%u nits\n",
meta.hdr_eotf, meta.hdr_max_nits);
}
drmModeFreeFB2(fb2);
static uint32_t seq_counter = 0;
seq_counter++;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
meta.seq = seq_counter;
meta.timestamp_ms = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
int ret;
if (meta.modifier != 0 || is_virtio) {
int prime_fd = -1;
int prime_ret = drmPrimeHandleToFD(drm_fd, gem_handle,
DRM_CLOEXEC, &prime_fd);
if (prime_ret == 0 && prime_fd >= 0) {
meta.flags = FLAG_HAS_DMABUF;
if (virtio_virgl) {
meta.flags |= FLAG_VIRGL;
}
meta.data_size = 0;
ret = send_fd(sock, prime_fd, &meta, sizeof(meta));
close(prime_fd);
return ret;
}
if (virtio_virgl) {
return send_error(sock, "virgl DMA-BUF export failed "
"(no usable CPU readback for a host scanout)");
}
fprintf(stderr,
"drmtap-helper: drmPrimeHandleToFD failed (%d), falling back to pixels\n",
prime_ret);
}
struct drm_mode_map_dumb map = {0};
map.handle = gem_handle;
if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
return send_error_errno(sock, "MODE_MAP_DUMB failed");
}
mapped = mmap(NULL, mapped_size, PROT_READ, MAP_SHARED,
drm_fd, map.offset);
if (mapped == MAP_FAILED) {
return send_error_errno(sock, "mmap failed");
}
meta.flags = 0;
meta.data_size = (uint32_t)mapped_size;
{
static int frame_num = 0;
frame_num++;
if (frame_num <= 5 || frame_num % 60 == 0) {
uint32_t *pixels = (uint32_t *)mapped;
uint32_t stride_px = meta.stride / 4;
uint32_t p_top = pixels[960 < (meta.stride/4) ? 960 : 0];
uint32_t p_clock = (94 * stride_px + 960 < mapped_size/4) ?
pixels[94 * stride_px + 960] : 0;
uint32_t p_mid = (500 * stride_px + 960 < mapped_size/4) ?
pixels[500 * stride_px + 960] : 0;
fprintf(stderr,
"drmtap-helper: frame=%d fb=%u mod=0x%lx top=%08x clock=%08x mid=%08x\n",
frame_num, meta.fb_id, (unsigned long)meta.modifier,
p_top, p_clock, p_mid);
}
}
ret = send_all(sock, &meta, sizeof(meta));
if (ret == 0) {
ret = send_all(sock, mapped, mapped_size);
}
munmap(mapped, mapped_size);
return ret;
}
static int device_path_allowed(const char *path, char *resolved) {
if (!realpath(path, resolved)) {
return 0;
}
return strncmp(resolved, "/dev/dri/", 9) == 0;
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
if (fcntl(HELPER_SOCKET_FD, F_GETFD) < 0) {
fprintf(stderr,
"drmtap-helper: fd %d not valid — must be spawned by "
"libdrmtap, not run directly\n", HELPER_SOCKET_FD);
return 1;
}
{
struct ucred peer;
socklen_t plen = sizeof(peer);
if (getsockopt(HELPER_SOCKET_FD, SOL_SOCKET, SO_PEERCRED,
&peer, &plen) != 0) {
fprintf(stderr,
"drmtap-helper: fd %d is not a socket — must be spawned by "
"libdrmtap, not run directly\n", HELPER_SOCKET_FD);
return 1;
}
if (peer.uid != getuid()) {
fprintf(stderr,
"drmtap-helper: refusing to serve peer uid %u (expected %u)\n",
(unsigned)peer.uid, (unsigned)getuid());
return 1;
}
}
int sock = HELPER_SOCKET_FD;
const char *device = "/dev/dri/card0";
if (argc > 1) {
device = argv[1];
}
const char *env_dev = getenv("DRM_DEVICE");
if (env_dev) {
device = env_dev;
}
char resolved_device[PATH_MAX];
if (!device_path_allowed(device, resolved_device)) {
fprintf(stderr,
"drmtap-helper: refusing device path '%s' "
"(must resolve under /dev/dri/)\n", device);
return 1;
}
device = resolved_device;
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
perror("drmtap-helper: prctl(PR_SET_NO_NEW_PRIVS)");
return 1;
}
int drm_fd = open(device, O_RDONLY | O_CLOEXEC);
if (drm_fd < 0) {
fprintf(stderr, "drmtap-helper: failed to open %s: %s\n",
device, strerror(errno));
return 1;
}
drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
int is_virtio = 0;
drmVersion *ver = drmGetVersion(drm_fd);
if (ver) {
if (ver->name_len >= 9 &&
strncmp(ver->name, "virtio_gpu", ver->name_len) == 0) {
is_virtio = 1;
}
drmFreeVersion(ver);
}
fprintf(stderr, "drmtap-helper: persistent fd=%d device=%s virtio=%d\n",
drm_fd, device, is_virtio);
#ifdef HAVE_LIBCAP
if (drop_caps() != 0) {
fprintf(stderr,
"drmtap-helper: refusing to run: could not drop capabilities\n");
return 1;
}
#endif
#ifdef HAVE_SECCOMP
if (install_seccomp() != 0) {
fprintf(stderr,
"drmtap-helper: refusing to run: could not install seccomp filter\n");
return 1;
}
#endif
while (1) {
struct helper_cmd_grab hcmd;
if (recv_all(sock, &hcmd, sizeof(hcmd)) != 0) {
break;
}
switch (hcmd.cmd) {
case CMD_GRAB:
grab_and_send(sock, drm_fd, hcmd.crtc_id, is_virtio);
break;
case CMD_GET_CURSOR:
cursor_and_send(sock, drm_fd, hcmd.crtc_id);
break;
case CMD_QUIT:
goto done;
default:
send_error(sock, "unknown command");
break;
}
}
done:
close(sock);
return 0;
}