#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <linux/dma-buf.h>
#include "drmtap_internal.h"
#include "wire.h"
static int get_property_value(int fd, uint32_t object_id, uint32_t object_type,
const char *name, uint64_t *value) {
drmModeObjectProperties *props = drmModeObjectGetProperties(
fd, object_id, object_type);
if (!props) {
return -1;
}
int found = -1;
for (uint32_t i = 0; i < props->count_props; i++) {
drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]);
if (prop) {
if (strcmp(prop->name, name) == 0) {
*value = props->prop_values[i];
found = 0;
}
drmModeFreeProperty(prop);
if (found == 0) {
break;
}
}
}
drmModeFreeObjectProperties(props);
return found;
}
static uint32_t find_cursor_plane(drmtap_ctx *ctx) {
drmModePlaneRes *planes = drmModeGetPlaneResources(ctx->drm_fd);
if (!planes) {
return 0;
}
uint32_t target_crtc = ctx->crtc_id;
uint32_t result = 0;
for (uint32_t i = 0; i < planes->count_planes; i++) {
drmModePlane *plane = drmModeGetPlane(ctx->drm_fd, planes->planes[i]);
if (!plane) {
continue;
}
int crtc_ok = (target_crtc != 0) ? (plane->crtc_id == target_crtc)
: (plane->crtc_id != 0);
if (!crtc_ok) {
drmModeFreePlane(plane);
continue;
}
uint64_t type_val = 0;
if (get_property_value(ctx->drm_fd, plane->plane_id,
DRM_MODE_OBJECT_PLANE, "type",
&type_val) == 0) {
if (type_val == DRM_PLANE_TYPE_CURSOR) {
result = plane->plane_id;
drmModeFreePlane(plane);
break;
}
}
drmModeFreePlane(plane);
}
drmModeFreePlaneResources(planes);
return result;
}
int drmtap_cursor_needs_helper(int fb2_ok, int err, int had_handle) {
if (!fb2_ok) {
return err == EACCES || err == EPERM;
}
return had_handle == 0;
}
int drmtap_get_cursor(drmtap_ctx *ctx, drmtap_cursor_info *cursor) {
if (!ctx || !cursor) {
return -EINVAL;
}
memset(cursor, 0, sizeof(*cursor));
if (ctx->helper_fd >= 0) {
return drmtap_helper_get_cursor(ctx, cursor);
}
uint32_t cursor_plane_id = find_cursor_plane(ctx);
if (cursor_plane_id == 0) {
cursor->visible = 0;
drmtap_cursor_set_hot_provenance(cursor, 0);
return 0;
}
drmModePlane *plane = drmModeGetPlane(ctx->drm_fd, cursor_plane_id);
if (!plane) {
return -ENODEV;
}
if (plane->fb_id == 0) {
cursor->visible = 0;
cursor->pixels = NULL;
drmtap_cursor_set_hot_provenance(cursor, 0);
drmModeFreePlane(plane);
return 0;
}
cursor->visible = 1;
uint64_t crtc_x = 0, crtc_y = 0;
get_property_value(ctx->drm_fd, cursor_plane_id,
DRM_MODE_OBJECT_PLANE, "CRTC_X", &crtc_x);
get_property_value(ctx->drm_fd, cursor_plane_id,
DRM_MODE_OBJECT_PLANE, "CRTC_Y", &crtc_y);
cursor->x = (int32_t)crtc_x;
cursor->y = (int32_t)crtc_y;
uint64_t hot_x = 0, hot_y = 0;
int have_hot_x = get_property_value(ctx->drm_fd, cursor_plane_id,
DRM_MODE_OBJECT_PLANE, "HOTSPOT_X", &hot_x) == 0;
int have_hot_y = get_property_value(ctx->drm_fd, cursor_plane_id,
DRM_MODE_OBJECT_PLANE, "HOTSPOT_Y", &hot_y) == 0;
cursor->hot_x = (int32_t)hot_x;
cursor->hot_y = (int32_t)hot_y;
drmtap_cursor_set_hot_provenance(cursor,
wire_hot_measured(have_hot_x, have_hot_y));
drmModeFB2 *fb2 = drmModeGetFB2(ctx->drm_fd, plane->fb_id);
if (!fb2) {
int err = errno;
uint32_t fb_id = plane->fb_id;
drmModeFreePlane(plane);
if (drmtap_cursor_needs_helper(0, err, 0)) {
return drmtap_helper_get_cursor(ctx, cursor);
}
drmtap_debug_log(ctx,
"cursor: fb %u unreadable (%s); transient, keeping the current shape",
fb_id, strerror(err));
cursor->pixels = NULL;
return 0;
}
cursor->width = fb2->width;
cursor->height = fb2->height;
int had_handle = (fb2->handles[0] != 0);
if (had_handle) {
int prime_fd = -1;
int ret = drmPrimeHandleToFD(ctx->drm_fd, fb2->handles[0],
O_RDONLY | O_CLOEXEC, &prime_fd);
if (ret == 0 && prime_fd >= 0) {
uint32_t stride = fb2->pitches[0];
if (fb2->width <= 256 && fb2->height <= 256 &&
stride != 0 && stride >= fb2->width * 4) {
size_t map_size = (size_t)stride * fb2->height;
void *mapped = mmap(NULL, map_size, PROT_READ, MAP_SHARED,
prime_fd, 0);
if (mapped != MAP_FAILED) {
struct dma_buf_sync sync = {
.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ
};
int sync_started =
drmIoctl(prime_fd, DMA_BUF_IOCTL_SYNC, &sync) == 0;
if (!sync_started) {
drmtap_debug_log(ctx,
"cursor: DMA_BUF_SYNC_START failed (%s); reading "
"without cache invalidation", strerror(errno));
}
size_t tight = (size_t)fb2->width * fb2->height * 4;
cursor->pixels = malloc(tight);
if (cursor->pixels) {
for (uint32_t y = 0; y < fb2->height; y++) {
memcpy((uint8_t *)cursor->pixels +
(size_t)y * fb2->width * 4,
(const uint8_t *)mapped + (size_t)y * stride,
(size_t)fb2->width * 4);
}
}
if (sync_started) {
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ;
if (drmIoctl(prime_fd, DMA_BUF_IOCTL_SYNC, &sync) != 0) {
drmtap_debug_log(ctx,
"cursor: DMA_BUF_SYNC_END failed (%s)",
strerror(errno));
}
}
munmap(mapped, map_size);
}
}
close(prime_fd);
}
}
if (fb2->handles[0] != 0) {
struct drm_gem_close gc = { .handle = fb2->handles[0] };
drmIoctl(ctx->drm_fd, DRM_IOCTL_GEM_CLOSE, &gc);
}
drmModeFreeFB2(fb2);
drmModeFreePlane(plane);
if (cursor->visible && cursor->pixels == NULL) {
if (drmtap_cursor_needs_helper(1, 0, had_handle)) {
return drmtap_helper_get_cursor(ctx, cursor);
}
drmtap_debug_log(ctx,
"cursor: fb readable but no pixels this poll; transient, keeping shape");
}
drmtap_debug_log(ctx, "cursor: %ux%u at (%d,%d) hotspot=(%d,%d) %s",
cursor->width, cursor->height,
cursor->x, cursor->y,
cursor->hot_x, cursor->hot_y,
cursor->visible ? "visible" : "hidden");
return 0;
}
void drmtap_cursor_release(drmtap_ctx *ctx, drmtap_cursor_info *cursor) {
(void)ctx;
if (!cursor) {
return;
}
free(cursor->pixels);
cursor->pixels = NULL;
cursor->_priv = NULL;
}
int drmtap_cursor_hotspot_valid(const drmtap_cursor_info *cursor, int *valid) {
if (!cursor || !valid) {
return -EINVAL;
}
uintptr_t flags = (uintptr_t)cursor->_priv;
if (!(flags & CURSOR_PRIV_HOT_ANSWERED)) {
return -ENOTSUP;
}
*valid = (flags & CURSOR_PRIV_HOT_MEASURED) ? 1 : 0;
return 0;
}