/*
|
* Copyright (c) 2021, Jeffy Chen <jeffy.chen@rock-chips.com>
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation; either version 2 of the License, or
|
* (at your option) any later version.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*/
|
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <inttypes.h>
|
#include <pthread.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <unistd.h>
|
#include <sys/mman.h>
|
#include <sys/stat.h>
|
|
#include <xf86drm.h>
|
#include <xf86drmMode.h>
|
|
#include <gbm.h>
|
|
#include "drm_common.h"
|
#include "drm_egl.h"
|
|
#define DRM_CURSOR_CONFIG_FILE "/etc/drm-cursor.conf"
|
#define OPT_DEBUG "debug="
|
#define OPT_LOG_FILE "log-file="
|
#define OPT_HIDE "hide="
|
#define OPT_ALLOW_OVERLAY "allow-overlay="
|
#define OPT_PREFER_AFBC "prefer-afbc="
|
#define OPT_PREFER_PLANE "prefer-plane="
|
#define OPT_PREFER_PLANES "prefer-planes="
|
#define OPT_CRTC_BLOCKLIST "crtc-blocklist="
|
#define OPT_NUM_SURFACES "num-surfaces="
|
#define OPT_MAX_FPS "max-fps="
|
#define OPT_ATOMIC "atomic="
|
|
#define DRM_MAX_CRTCS 8
|
|
typedef enum {
|
PLANE_PROP_type = 0,
|
PLANE_PROP_IN_FORMATS,
|
PLANE_PROP_zpos,
|
PLANE_PROP_ZPOS,
|
PLANE_PROP_ASYNC_COMMIT,
|
PLANE_PROP_CRTC_ID,
|
PLANE_PROP_FB_ID,
|
PLANE_PROP_SRC_X,
|
PLANE_PROP_SRC_Y,
|
PLANE_PROP_SRC_W,
|
PLANE_PROP_SRC_H,
|
PLANE_PROP_CRTC_X,
|
PLANE_PROP_CRTC_Y,
|
PLANE_PROP_CRTC_W,
|
PLANE_PROP_CRTC_H,
|
PLANE_PROP_MAX,
|
} drm_plane_prop;
|
|
static const char *drm_plane_prop_names[] = {
|
[PLANE_PROP_type] = "type",
|
[PLANE_PROP_IN_FORMATS] = "IN_FORMATS",
|
[PLANE_PROP_zpos] = "zpos",
|
[PLANE_PROP_ZPOS] = "ZPOS",
|
[PLANE_PROP_ASYNC_COMMIT] = "ASYNC_COMMIT",
|
[PLANE_PROP_CRTC_ID] = "CRTC_ID",
|
[PLANE_PROP_FB_ID] = "FB_ID",
|
[PLANE_PROP_SRC_X] = "SRC_X",
|
[PLANE_PROP_SRC_Y] = "SRC_Y",
|
[PLANE_PROP_SRC_W] = "SRC_W",
|
[PLANE_PROP_SRC_H] = "SRC_H",
|
[PLANE_PROP_CRTC_X] = "CRTC_X",
|
[PLANE_PROP_CRTC_Y] = "CRTC_Y",
|
[PLANE_PROP_CRTC_W] = "CRTC_W",
|
[PLANE_PROP_CRTC_H] = "CRTC_H",
|
};
|
|
typedef struct {
|
uint32_t plane_id;
|
int cursor_plane;
|
int can_afbc;
|
int can_linear;
|
drmModePlane *plane;
|
drmModeObjectProperties *props;
|
int prop_ids[PLANE_PROP_MAX];
|
} drm_plane;
|
|
#define REQ_SET_CURSOR (1 << 0)
|
#define REQ_MOVE_CURSOR (1 << 1)
|
|
typedef struct {
|
uint32_t handle;
|
uint32_t fb;
|
|
int width;
|
int height;
|
|
int x;
|
int y;
|
|
int off_x;
|
int off_y;
|
|
int request;
|
} drm_cursor_state;
|
|
typedef enum {
|
IDLE = 0,
|
ERROR,
|
PENDING,
|
} drm_thread_state;
|
|
typedef struct {
|
uint32_t crtc_id;
|
uint32_t crtc_pipe;
|
|
int width;
|
int height;
|
|
drm_plane *plane;
|
uint32_t prefer_plane_id;
|
|
drm_cursor_state cursor_next;
|
drm_cursor_state cursor_curr;
|
|
pthread_t thread;
|
pthread_cond_t cond;
|
pthread_mutex_t mutex;
|
drm_thread_state state;
|
|
void *egl_ctx;
|
|
int verified;
|
|
int use_afbc_modifier;
|
int blocked;
|
int async_commit;
|
|
uint64_t last_update_time;
|
} drm_crtc;
|
|
typedef struct {
|
int fd;
|
|
drm_crtc crtcs[DRM_MAX_CRTCS];
|
int num_crtcs;
|
|
drmModePlaneResPtr pres;
|
drmModeRes *res;
|
|
int prefer_afbc_modifier;
|
int allow_overlay;
|
int num_surfaces;
|
int inited;
|
int atomic;
|
int hide;
|
uint64_t min_interval;
|
|
char *configs;
|
} drm_ctx;
|
|
static drm_ctx g_drm_ctx = { 0, };
|
drm_private int g_drm_debug = 0;
|
drm_private FILE *g_log_fp = NULL;
|
|
static inline uint64_t drm_curr_time(void)
|
{
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
return tv.tv_sec + tv.tv_usec / 1000;
|
}
|
|
static int drm_plane_get_prop(drm_ctx *ctx, drm_plane *plane, drm_plane_prop p)
|
{
|
drmModePropertyPtr prop;
|
uint32_t i;
|
|
if (plane->prop_ids[p])
|
return plane->prop_ids[p];
|
|
for (i = 0; i < plane->props->count_props; i++) {
|
prop = drmModeGetProperty(ctx->fd, plane->props->props[i]);
|
if (prop && !strcmp(prop->name, drm_plane_prop_names[p])) {
|
drmModeFreeProperty(prop);
|
plane->prop_ids[p] = i;
|
return i;
|
}
|
drmModeFreeProperty(prop);
|
}
|
|
return -1;
|
}
|
|
static int drm_atomic_add_plane_prop(drm_ctx *ctx, drmModeAtomicReq *request,
|
drm_plane *plane, drm_plane_prop p,
|
uint64_t value)
|
{
|
int prop_idx = drm_plane_get_prop(ctx, plane, p);
|
if (prop_idx < 0)
|
return -1;
|
|
return drmModeAtomicAddProperty(request, plane->plane_id,
|
plane->props->props[prop_idx], value);
|
}
|
|
static int drm_set_plane(drm_ctx *ctx, drm_crtc *crtc, drm_plane *plane,
|
uint32_t fb, int x, int y, int w, int h)
|
{
|
drmModeAtomicReq *req;
|
int ret = 0;
|
|
if (plane->cursor_plane || crtc->async_commit || !ctx->atomic)
|
goto legacy;
|
|
req = drmModeAtomicAlloc();
|
if (!req)
|
goto legacy;
|
|
if (!fb) {
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_CRTC_ID, 0);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_FB_ID, 0);
|
} else {
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane,
|
PLANE_PROP_CRTC_ID, crtc->crtc_id);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_FB_ID, fb);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_SRC_X, 0);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_SRC_Y, 0);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane,
|
PLANE_PROP_SRC_W, w << 16);
|
ret |= drm_atomic_add_plane_prop(ctx, req,
|
plane, PLANE_PROP_SRC_H, h << 16);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_CRTC_X, x);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_CRTC_Y, y);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_CRTC_W, w);
|
ret |= drm_atomic_add_plane_prop(ctx, req, plane, PLANE_PROP_CRTC_H, h);
|
}
|
|
ret |= drmModeAtomicCommit(ctx->fd, req, DRM_MODE_ATOMIC_NONBLOCK, NULL);
|
drmModeAtomicFree(req);
|
|
if (ret >= 0)
|
return 0;
|
|
legacy:
|
if (ret < 0 && ctx->atomic) {
|
DRM_ERROR("CRTC[%d]: failed to do atomic commit (%d)\n",
|
crtc->crtc_id, errno);
|
ctx->atomic = 0;
|
}
|
return drmModeSetPlane(ctx->fd, plane->plane_id, crtc->crtc_id, fb, 0,
|
x, y, w, h, 0, 0, w << 16, h << 16);
|
}
|
|
static int drm_plane_get_prop_value(drm_ctx *ctx, drm_plane *plane,
|
drm_plane_prop p, uint64_t *value)
|
{
|
int prop_idx = drm_plane_get_prop(ctx, plane, p);
|
if (prop_idx < 0)
|
return -1;
|
|
*value = plane->props->prop_values[prop_idx];
|
return 0;
|
}
|
|
static int drm_plane_set_prop_max(drm_ctx *ctx, drm_plane *plane,
|
drm_plane_prop p)
|
{
|
drmModePropertyPtr prop;
|
int prop_idx = drm_plane_get_prop(ctx, plane, p);
|
if (prop_idx < 0)
|
return -1;
|
|
prop = drmModeGetProperty(ctx->fd, plane->props->props[prop_idx]);
|
drmModeObjectSetProperty (ctx->fd, plane->plane_id,
|
DRM_MODE_OBJECT_PLANE,
|
plane->props->props[prop_idx],
|
prop->values[prop->count_values - 1]);
|
DRM_DEBUG("set plane %d prop: %s to max: %"PRIu64"\n",
|
plane->plane_id, drm_plane_prop_names[p],
|
prop->values[prop->count_values - 1]);
|
drmModeFreeProperty(prop);
|
return 0;
|
}
|
|
static void drm_free_plane(drm_plane *plane)
|
{
|
drmModeFreeObjectProperties(plane->props);
|
drmModeFreePlane(plane->plane);
|
free(plane);
|
}
|
|
static void drm_plane_update_format(drm_ctx *ctx, drm_plane *plane)
|
{
|
drmModePropertyBlobPtr blob;
|
struct drm_format_modifier_blob *header;
|
struct drm_format_modifier *modifiers;
|
uint32_t *formats;
|
uint64_t value;
|
uint32_t i, j;
|
|
plane->can_afbc = plane->can_linear = 0;
|
|
/* Check formats */
|
for (i = 0; i < plane->plane->count_formats; i++) {
|
if (plane->plane->formats[i] == DRM_FORMAT_ARGB8888)
|
break;
|
}
|
if (i == plane->plane->count_formats)
|
return;
|
|
if (drm_plane_get_prop_value(ctx, plane, PLANE_PROP_IN_FORMATS, &value) < 0) {
|
/* No in_formats */
|
plane->can_linear = 1;
|
return;
|
}
|
|
blob = drmModeGetPropertyBlob(ctx->fd, value);
|
if (!blob)
|
return;
|
|
header = blob->data;
|
formats = (uint32_t *) ((char *) header + header->formats_offset);
|
modifiers = (struct drm_format_modifier *)
|
((char *) header + header->modifiers_offset);
|
|
/* Check in_formats */
|
for (i = 0; i < header->count_formats; i++) {
|
if (formats[i] == DRM_FORMAT_ARGB8888)
|
break;
|
}
|
if (i == header->count_formats)
|
goto out;
|
|
if (!header->count_modifiers) {
|
plane->can_linear = 1;
|
goto out;
|
}
|
|
/* Check modifiers */
|
for (j = 0; j < header->count_modifiers; j++) {
|
struct drm_format_modifier *mod = &modifiers[j];
|
|
if ((i < mod->offset) || (i > mod->offset + 63))
|
continue;
|
if (!(mod->formats & (1 << (i - mod->offset))))
|
continue;
|
|
if (mod->modifier == DRM_AFBC_MODIFIER)
|
plane->can_afbc = 1;
|
|
if (mod->modifier == DRM_FORMAT_MOD_LINEAR)
|
plane->can_linear = 1;
|
}
|
|
out:
|
drmModeFreePropertyBlob(blob);
|
}
|
|
static drm_plane *drm_get_plane(drm_ctx *ctx, uint32_t plane_id)
|
{
|
drm_plane *plane = calloc(1, sizeof(*plane));
|
if (!plane)
|
return NULL;
|
|
plane->plane_id = plane_id;
|
plane->plane = drmModeGetPlane(ctx->fd, plane_id);
|
if (!plane->plane)
|
goto err;
|
|
plane->props = drmModeObjectGetProperties(ctx->fd, plane_id,
|
DRM_MODE_OBJECT_PLANE);
|
if (!plane->props)
|
goto err;
|
|
drm_plane_update_format(ctx, plane);
|
return plane;
|
err:
|
drm_free_plane(plane);
|
return NULL;
|
}
|
|
static void drm_load_configs(drm_ctx *ctx)
|
{
|
struct stat st;
|
const char *file = DRM_CURSOR_CONFIG_FILE;
|
char *ptr, *tmp;
|
int fd;
|
|
if (stat(file, &st) < 0)
|
return;
|
|
fd = open(file, O_RDONLY);
|
if (fd < 0)
|
return;
|
|
ptr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
if (ptr == MAP_FAILED)
|
goto out_close_fd;
|
|
ctx->configs = malloc(st.st_size + 1);
|
if (!ctx->configs)
|
goto out_unmap;
|
|
memcpy(ctx->configs, ptr, st.st_size);
|
ctx->configs[st.st_size] = '\0';
|
|
tmp = ctx->configs;
|
while ((tmp = strchr(tmp, '#'))) {
|
while (*tmp != '\n' && *tmp != '\0')
|
*tmp++ = '\n';
|
}
|
|
out_unmap:
|
munmap(ptr, st.st_size);
|
out_close_fd:
|
close(fd);
|
}
|
|
static const char *drm_get_config(drm_ctx *ctx, const char *name)
|
{
|
static char buf[4096];
|
const char *config;
|
|
if (!ctx->configs)
|
return NULL;
|
|
config = strstr(ctx->configs, name);
|
if (!config)
|
return NULL;
|
|
sscanf(config + strlen(name), "%4095s", buf);
|
return buf;
|
}
|
|
static int drm_get_config_int(drm_ctx *ctx, const char *name, int def)
|
{
|
const char *config = drm_get_config(ctx, name);
|
|
if (config)
|
return atoi(config);
|
|
return def;
|
}
|
|
static drm_ctx *drm_get_ctx(int fd)
|
{
|
drm_ctx *ctx = &g_drm_ctx;
|
uint32_t prefer_planes[DRM_MAX_CRTCS] = { 0, };
|
uint32_t prefer_plane = 0;
|
uint32_t i, max_fps, count_crtcs;
|
const char *config;
|
|
if (ctx->inited)
|
return ctx;
|
|
/* Failed already */
|
if (ctx->fd < 0)
|
return NULL;
|
|
ctx->fd = dup(fd);
|
if (ctx->fd < 0)
|
return NULL;
|
|
drm_load_configs(ctx);
|
|
g_drm_debug = drm_get_config_int(ctx, OPT_DEBUG, 0);
|
|
if (getenv("DRM_DEBUG") || !access("/tmp/.drm_cursor_debug", F_OK))
|
g_drm_debug = 1;
|
|
if (!(config = getenv("DRM_CURSOR_LOG_FILE")))
|
config = drm_get_config(ctx, OPT_LOG_FILE);
|
|
g_log_fp = fopen(config ? config : "/var/log/drm-cursor.log", "wb+");
|
|
ctx->atomic = drm_get_config_int(ctx, OPT_ATOMIC, 1);
|
DRM_INFO("atomic drm API %s\n", ctx->atomic ? "enabled" : "disabled");
|
|
ctx->hide = drm_get_config_int(ctx, OPT_HIDE, 0);
|
if (ctx->hide)
|
DRM_INFO("invisible cursors\n");
|
|
#ifdef PREFER_AFBC_MODIFIER
|
ctx->prefer_afbc_modifier = 1;
|
#endif
|
|
ctx->prefer_afbc_modifier =
|
drm_get_config_int(ctx, OPT_PREFER_AFBC, ctx->prefer_afbc_modifier);
|
|
if (ctx->prefer_afbc_modifier)
|
DRM_DEBUG("prefer ARM AFBC modifier\n");
|
|
ctx->allow_overlay = drm_get_config_int(ctx, OPT_ALLOW_OVERLAY, 0);
|
|
if (ctx->allow_overlay)
|
DRM_DEBUG("allow overlay planes\n");
|
|
drmSetClientCap(ctx->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
|
ctx->num_surfaces = drm_get_config_int(ctx, OPT_NUM_SURFACES, 8);
|
|
max_fps = drm_get_config_int(ctx, OPT_MAX_FPS, 0);
|
if (max_fps <= 0)
|
max_fps = 60;
|
|
ctx->min_interval = 1000 / max_fps;
|
if (ctx->min_interval)
|
ctx->min_interval--;
|
|
DRM_INFO("max fps: %d\n", max_fps);
|
|
ctx->res = drmModeGetResources(ctx->fd);
|
if (!ctx->res)
|
goto err_free_configs;
|
|
ctx->pres = drmModeGetPlaneResources(ctx->fd);
|
if (!ctx->pres)
|
goto err_free_res;
|
|
count_crtcs = ctx->res->count_crtcs;
|
|
/* Allow specifying prefer plane */
|
if ((config = getenv("DRM_CURSOR_PREFER_PLANE")))
|
prefer_plane = atoi(config);
|
else
|
prefer_plane = drm_get_config_int(ctx, OPT_PREFER_PLANE, 0);
|
|
/* Allow specifying prefer planes */
|
if (!(config = getenv("DRM_CURSOR_PREFER_PLANES")))
|
config = drm_get_config(ctx, OPT_PREFER_PLANES);
|
for (i = 0; config && i < count_crtcs; i++) {
|
prefer_planes[i] = atoi(config);
|
|
config = strchr(config, ',');
|
if (config)
|
config++;
|
}
|
|
/* Fetch all CRTCs */
|
for (i = 0; i < count_crtcs; i++) {
|
drmModeCrtcPtr c = drmModeGetCrtc(ctx->fd, ctx->res->crtcs[i]);
|
drm_crtc *crtc = &ctx->crtcs[ctx->num_crtcs];
|
|
if (!c)
|
continue;
|
|
crtc->crtc_id = c->crtc_id;
|
crtc->crtc_pipe = i;
|
crtc->prefer_plane_id = prefer_planes[i] ? prefer_planes[i] : prefer_plane;
|
|
DRM_DEBUG("found %d CRTC: %d(%d) (%dx%d) prefer plane: %d\n",
|
ctx->num_crtcs, c->crtc_id, i, c->width, c->height,
|
crtc->prefer_plane_id);
|
|
ctx->num_crtcs++;
|
drmModeFreeCrtc(c);
|
}
|
|
DRM_DEBUG("found %d CRTCs\n", ctx->num_crtcs);
|
|
if (!ctx->num_crtcs)
|
goto err_free_pres;
|
|
config = drm_get_config(ctx, OPT_CRTC_BLOCKLIST);
|
for (i = 0; config && i < count_crtcs; i++) {
|
uint32_t crtc_id = atoi(config);
|
|
for (int j = 0; j < ctx->num_crtcs; j++) {
|
drm_crtc *crtc = &ctx->crtcs[j];
|
if (crtc->crtc_id != crtc_id)
|
continue;
|
|
DRM_DEBUG("CRTC: %d blocked\n", crtc_id);
|
crtc->blocked = 1;
|
}
|
|
config = strchr(config, ',');
|
if (config)
|
config++;
|
}
|
|
if (g_drm_debug) {
|
/* Dump planes for debugging */
|
for (i = 0; i < ctx->pres->count_planes; i++) {
|
drm_plane *plane = drm_get_plane(ctx, ctx->pres->planes[i]);
|
char *type;
|
uint64_t value = 0;
|
|
if (!plane)
|
continue;
|
|
drm_plane_get_prop_value(ctx, plane, PLANE_PROP_type, &value);
|
switch (value) {
|
case DRM_PLANE_TYPE_PRIMARY:
|
type = "primary";
|
break;
|
case DRM_PLANE_TYPE_OVERLAY:
|
type = "overlay";
|
break;
|
case DRM_PLANE_TYPE_CURSOR:
|
type = "cursor ";
|
break;
|
default:
|
type = "unknown";
|
break;
|
}
|
|
DRM_DEBUG("found plane: %d[%s] crtcs: 0x%x %s%s\n",
|
plane->plane_id, type, plane->plane->possible_crtcs,
|
plane->can_linear ? "(ARGB)" : "",
|
plane->can_afbc ? "(AFBC)" : "");
|
|
drm_free_plane(plane);
|
}
|
}
|
|
DRM_INFO("using libdrm-cursor (%s)\n", LIBDRM_CURSOR_VERSION);
|
|
ctx->inited = 1;
|
return ctx;
|
|
err_free_pres:
|
drmModeFreePlaneResources(ctx->pres);
|
err_free_res:
|
drmModeFreeResources(ctx->res);
|
err_free_configs:
|
free(ctx->configs);
|
close(ctx->fd);
|
ctx->fd = -1;
|
return NULL;
|
}
|
|
#define drm_crtc_bind_plane_force(ctx, crtc, plane) \
|
drm_crtc_bind_plane(ctx, crtc, plane, 1)
|
|
#define drm_crtc_bind_plane_cursor(ctx, crtc, plane) \
|
drm_crtc_bind_plane(ctx, crtc, plane, 0)
|
|
static int drm_crtc_bind_plane(drm_ctx *ctx, drm_crtc *crtc, uint32_t plane_id,
|
int allow_overlay)
|
{
|
drm_plane *plane;
|
uint64_t value;
|
int i;
|
|
/* CRTC already assigned */
|
if (crtc->plane)
|
return 1;
|
|
/* Plane already assigned */
|
for (i = 0; i < ctx->num_crtcs; i++) {
|
if (ctx->crtcs[i].plane && ctx->crtcs[i].plane->plane_id == plane_id)
|
return -1;
|
}
|
|
plane = drm_get_plane(ctx, plane_id);
|
if (!plane)
|
return -1;
|
|
/* Unable to use */
|
if (!plane->can_afbc && !plane->can_linear)
|
goto err;
|
|
/* Not for this CRTC */
|
if (!(plane->plane->possible_crtcs & (1 << crtc->crtc_pipe)))
|
goto err;
|
|
/* Not using primary planes */
|
if (drm_plane_get_prop_value(ctx, plane, PLANE_PROP_type, &value) < 0)
|
goto err;
|
|
if (value == DRM_PLANE_TYPE_PRIMARY)
|
goto err;
|
|
/* Check for overlay plane */
|
if (!allow_overlay && value == DRM_PLANE_TYPE_OVERLAY)
|
goto err;
|
|
plane->cursor_plane = value == DRM_PLANE_TYPE_CURSOR;
|
if (plane->cursor_plane)
|
DRM_INFO("CRTC[%d]: using cursor plane\n", crtc->crtc_id);
|
|
if (ctx->prefer_afbc_modifier && plane->can_afbc)
|
crtc->use_afbc_modifier = 1;
|
else if (!plane->can_linear)
|
crtc->use_afbc_modifier = 1;
|
|
DRM_DEBUG("CRTC[%d]: bind plane: %d%s\n", crtc->crtc_id, plane->plane_id,
|
crtc->use_afbc_modifier ? "(AFBC)" : "");
|
|
crtc->plane = plane;
|
|
return 0;
|
err:
|
drm_free_plane(plane);
|
return -1;
|
}
|
|
static int drm_update_crtc(drm_ctx *ctx, drm_crtc *crtc)
|
{
|
drmModeCrtcPtr c;
|
|
c = drmModeGetCrtc(ctx->fd, crtc->crtc_id);
|
if (!c)
|
return -1;
|
|
crtc->width = c->width;
|
crtc->height = c->height;
|
|
drmModeFreeCrtc(c);
|
|
if (crtc->width > 0 && crtc->height > 0)
|
return 0;
|
|
return -1;
|
}
|
|
static int drm_crtc_update_offsets(drm_ctx *ctx, drm_crtc *crtc,
|
drm_cursor_state *cursor_state)
|
{
|
int width, height;
|
|
if (drm_update_crtc(ctx, crtc) < 0)
|
return -1;
|
|
width = crtc->width - cursor_state->width;
|
height = crtc->height - cursor_state->height;
|
|
cursor_state->off_x = cursor_state->off_y = 0;
|
|
if (cursor_state->x < 0)
|
cursor_state->off_x = cursor_state->x;
|
|
if (cursor_state->y < 0)
|
cursor_state->off_y = cursor_state->y;
|
|
if (cursor_state->x > width)
|
cursor_state->off_x = cursor_state->x - width;
|
|
if (cursor_state->y > height)
|
cursor_state->off_y = cursor_state->y - height;
|
|
return 0;
|
}
|
|
#define drm_crtc_disable_cursor(ctx, crtc) \
|
drm_crtc_update_cursor(ctx, crtc, NULL)
|
|
static int drm_crtc_update_cursor(drm_ctx *ctx, drm_crtc *crtc,
|
drm_cursor_state *cursor_state)
|
{
|
drm_plane *plane = crtc->plane;
|
uint32_t old_fb = crtc->cursor_curr.fb;
|
uint32_t fb;
|
int x, y, w, h, ret;
|
|
/* Disable */
|
if (!cursor_state) {
|
if (old_fb) {
|
DRM_DEBUG("CRTC[%d]: disabling cursor\n", crtc->crtc_id);
|
drm_set_plane(ctx, crtc, plane, 0, 0, 0, 0, 0);
|
drmModeRmFB(ctx->fd, old_fb);
|
}
|
|
memset(&crtc->cursor_curr, 0, sizeof(drm_cursor_state));
|
return 0;
|
}
|
|
/* Unchanged */
|
if (crtc->cursor_curr.fb == cursor_state->fb &&
|
crtc->cursor_curr.x == cursor_state->x &&
|
crtc->cursor_curr.y == cursor_state->y &&
|
crtc->cursor_curr.off_x == cursor_state->off_x &&
|
crtc->cursor_curr.off_y == cursor_state->off_y) {
|
crtc->cursor_curr = *cursor_state;
|
return 0;
|
}
|
|
fb = cursor_state->fb;
|
x = cursor_state->x - cursor_state->off_x;
|
y = cursor_state->y - cursor_state->off_y;
|
w = cursor_state->width;
|
h = cursor_state->height;
|
|
DRM_DEBUG("CRTC[%d]: setting fb: %d (%dx%d) on plane: %d at (%d,%d)\n",
|
crtc->crtc_id, fb, w, h, plane->plane_id, x, y);
|
|
ret = drm_set_plane(ctx, crtc, plane, fb, x, y, w, h);
|
if (ret)
|
DRM_ERROR("CRTC[%d]: failed to set plane (%d)\n", crtc->crtc_id, errno);
|
|
if (old_fb && old_fb != fb) {
|
DRM_DEBUG("CRTC[%d]: remove FB: %d\n", crtc->crtc_id, old_fb);
|
drmModeRmFB(ctx->fd, old_fb);
|
}
|
|
crtc->cursor_curr = *cursor_state;
|
return ret;
|
}
|
|
static int drm_crtc_create_fb(drm_ctx *ctx, drm_crtc *crtc,
|
drm_cursor_state *cursor_state)
|
{
|
uint32_t handle = cursor_state->handle;
|
int width = cursor_state->width;
|
int height = cursor_state->height;
|
int off_x = cursor_state->off_x;
|
int off_y = cursor_state->off_y;
|
|
DRM_DEBUG("CRTC[%d]: convert FB from %d (%dx%d) offset:(%d,%d)\n",
|
crtc->crtc_id, handle, width, height, off_x, off_y);
|
|
if (!crtc->egl_ctx) {
|
uint64_t modifier;
|
int format;
|
|
if (crtc->use_afbc_modifier) {
|
/* Mali only support AFBC with BGR formats now */
|
format = GBM_FORMAT_ABGR8888;
|
modifier = DRM_AFBC_MODIFIER;
|
} else {
|
format = GBM_FORMAT_ARGB8888;
|
modifier = 0;
|
}
|
|
crtc->egl_ctx = egl_init_ctx(ctx->fd, ctx->num_surfaces,
|
width, height, format, modifier);
|
if (!crtc->egl_ctx) {
|
DRM_ERROR("CRTC[%d]: failed to init egl ctx\n", crtc->crtc_id);
|
return -1;
|
}
|
}
|
|
cursor_state->fb =
|
egl_convert_fb(crtc->egl_ctx, handle, width, height, off_x, off_y);
|
if (!cursor_state->fb) {
|
DRM_ERROR("CRTC[%d]: failed to create FB\n", crtc->crtc_id);
|
return -1;
|
}
|
|
DRM_DEBUG("CRTC[%d]: created FB: %d\n", crtc->crtc_id, cursor_state->fb);
|
return 0;
|
}
|
|
static void *drm_crtc_thread_fn(void *data)
|
{
|
drm_ctx *ctx = drm_get_ctx(-1);
|
drm_crtc *crtc = data;
|
drm_plane *plane = crtc->plane;
|
drm_cursor_state cursor_state;
|
uint64_t duration;
|
char name[256];
|
|
DRM_DEBUG("CRTC[%d]: thread started\n", crtc->crtc_id);
|
|
/**
|
* The new DRM driver doesn't allow setting atomic cap for Xorg.
|
* Let's use a custom thread name to workaround that.
|
*/
|
snprintf(name, sizeof(name), "drm-cursor[%d]", crtc->crtc_id);
|
pthread_setname_np(crtc->thread, name);
|
|
if (!plane->cursor_plane) {
|
drmSetClientCap(ctx->fd, DRM_CLIENT_CAP_ATOMIC, 1);
|
|
/* Reflush props with atomic cap enabled */
|
drmModeFreeObjectProperties(plane->props);
|
plane->props = drmModeObjectGetProperties(ctx->fd, plane->plane_id,
|
DRM_MODE_OBJECT_PLANE);
|
if (!plane->props)
|
goto error;
|
|
/* Set maximum ZPOS */
|
drm_plane_set_prop_max(ctx, plane, PLANE_PROP_zpos);
|
drm_plane_set_prop_max(ctx, plane, PLANE_PROP_ZPOS);
|
|
/* Set async commit for Rockchip BSP kernel */
|
crtc->async_commit =
|
!drm_plane_set_prop_max(ctx, plane, PLANE_PROP_ASYNC_COMMIT);
|
if (crtc->async_commit)
|
DRM_INFO("CRTC[%d]: using async commit\n", crtc->crtc_id);
|
}
|
|
crtc->last_update_time = drm_curr_time();
|
|
while (1) {
|
/* Wait for new cursor state */
|
pthread_mutex_lock(&crtc->mutex);
|
while (crtc->state != PENDING)
|
pthread_cond_wait(&crtc->cond, &crtc->mutex);
|
|
cursor_state = crtc->cursor_next;
|
crtc->cursor_next.request = 0;
|
crtc->state = IDLE;
|
pthread_mutex_unlock(&crtc->mutex);
|
|
/* For edge moving */
|
if (drm_crtc_update_offsets(ctx, crtc, &cursor_state) < 0) {
|
/* Monitor disconnected */
|
DRM_DEBUG("CRTC[%d]: disconnected!\n", crtc->crtc_id);
|
|
/* Ignore requests */
|
drm_crtc_disable_cursor(ctx, crtc);
|
crtc->cursor_curr = cursor_state;
|
goto next;
|
}
|
|
if (cursor_state.request & REQ_SET_CURSOR) {
|
cursor_state.request &= ~REQ_SET_CURSOR;
|
|
/* Handle set-cursor */
|
DRM_DEBUG("CRTC[%d]: set new cursor %d (%dx%d)\n",
|
crtc->crtc_id, cursor_state.handle,
|
cursor_state.width, cursor_state.height);
|
|
if (!cursor_state.handle) {
|
drm_crtc_disable_cursor(ctx, crtc);
|
goto next;
|
}
|
|
if (drm_crtc_create_fb(ctx, crtc, &cursor_state) < 0)
|
goto error;
|
|
if (drm_crtc_update_cursor(ctx, crtc, &cursor_state) < 0) {
|
DRM_ERROR("CRTC[%d]: failed to set cursor\n", crtc->crtc_id);
|
goto error;
|
}
|
}
|
|
if (cursor_state.request & REQ_MOVE_CURSOR) {
|
cursor_state.request &= ~REQ_MOVE_CURSOR;
|
|
/* Handle move-cursor */
|
DRM_DEBUG("CRTC[%d]: move cursor to (%d[-%d],%d[-%d])\n",
|
crtc->crtc_id, cursor_state.x, cursor_state.off_x,
|
cursor_state.y, cursor_state.off_y);
|
|
if (!crtc->cursor_curr.handle) {
|
/* Pre-moving */
|
crtc->cursor_curr = cursor_state;
|
goto next;
|
} else if (crtc->cursor_curr.off_x != cursor_state.off_x ||
|
crtc->cursor_curr.off_y != cursor_state.off_y) {
|
/* Edge moving */
|
if (drm_crtc_create_fb(ctx, crtc, &cursor_state) < 0)
|
goto error;
|
} else {
|
/* Normal moving */
|
cursor_state.fb = crtc->cursor_curr.fb;
|
}
|
|
if (drm_crtc_update_cursor(ctx, crtc, &cursor_state) < 0) {
|
DRM_ERROR("CRTC[%d]: failed to move cursor\n", crtc->crtc_id);
|
goto error;
|
}
|
}
|
|
if (!crtc->verified && crtc->cursor_curr.fb) {
|
pthread_mutex_lock(&crtc->mutex);
|
DRM_INFO("CRTC[%d]: it works!\n", crtc->crtc_id);
|
crtc->verified = 1;
|
pthread_cond_signal(&crtc->cond);
|
pthread_mutex_unlock(&crtc->mutex);
|
}
|
|
next:
|
duration = drm_curr_time() - crtc->last_update_time;
|
if (duration < ctx->min_interval)
|
usleep((ctx->min_interval - duration) * 1000);
|
crtc->last_update_time = drm_curr_time();;
|
}
|
|
error:
|
if (crtc->egl_ctx)
|
egl_free_ctx(crtc->egl_ctx);
|
|
drm_crtc_disable_cursor(ctx, crtc);
|
|
pthread_mutex_lock(&crtc->mutex);
|
DRM_DEBUG("CRTC[%d]: thread error\n", crtc->crtc_id);
|
crtc->state = ERROR;
|
|
if (crtc->plane) {
|
drm_free_plane(crtc->plane);
|
crtc->plane = NULL;
|
}
|
|
pthread_cond_signal(&crtc->cond);
|
pthread_mutex_unlock(&crtc->mutex);
|
|
return NULL;
|
}
|
|
static int drm_crtc_prepare(drm_ctx *ctx, drm_crtc *crtc)
|
{
|
uint32_t i;
|
|
/* Update CRTC if unavailable */
|
if (crtc->width <= 0 || crtc->height <= 0)
|
drm_update_crtc(ctx, crtc);
|
|
/* CRTC already assigned */
|
if (crtc->plane)
|
return 1;
|
|
/* Try specific plane */
|
if (crtc->prefer_plane_id)
|
drm_crtc_bind_plane_force(ctx, crtc, crtc->prefer_plane_id);
|
|
/* Try cursor plane */
|
for (i = 0; !crtc->plane && i < ctx->pres->count_planes; i++)
|
drm_crtc_bind_plane_cursor(ctx, crtc, ctx->pres->planes[i]);
|
|
/* Fallback to any available overlay plane */
|
if (ctx->allow_overlay) {
|
for (i = ctx->pres->count_planes; !crtc->plane && i; i--)
|
drm_crtc_bind_plane_force(ctx, crtc, ctx->pres->planes[i - 1]);
|
}
|
|
if (!crtc->plane) {
|
DRM_ERROR("CRTC[%d]: failed to find any plane\n", crtc->crtc_id);
|
return -1;
|
}
|
|
crtc->state = IDLE;
|
|
pthread_cond_init(&crtc->cond, NULL);
|
pthread_mutex_init(&crtc->mutex, NULL);
|
pthread_create(&crtc->thread, NULL, drm_crtc_thread_fn, crtc);
|
|
return 0;
|
}
|
|
static drm_crtc *drm_get_crtc(drm_ctx *ctx, uint32_t crtc_id)
|
{
|
drm_crtc *crtc = NULL;
|
int i;
|
|
for (i = 0; i < ctx->num_crtcs; i++) {
|
crtc = &ctx->crtcs[i];
|
if (!crtc_id && drm_update_crtc(ctx, crtc) < 0)
|
continue;
|
|
if (crtc->blocked)
|
continue;
|
|
if (!crtc_id || crtc->crtc_id == crtc_id)
|
break;
|
}
|
|
if (i == ctx->num_crtcs) {
|
DRM_ERROR("CRTC[%d]: not available\n", crtc_id);
|
return NULL;
|
}
|
|
return crtc;
|
}
|
|
static int drm_set_cursor(int fd, uint32_t crtc_id, uint32_t handle,
|
uint32_t width, uint32_t height)
|
{
|
drm_crtc *crtc;
|
drm_ctx *ctx;
|
drm_cursor_state *cursor_next;
|
|
ctx = drm_get_ctx(fd);
|
if (!ctx)
|
return -1;
|
|
if (ctx->hide)
|
return 0;
|
|
crtc = drm_get_crtc(ctx, crtc_id);
|
if (!crtc)
|
return -1;
|
|
if (crtc->state == ERROR || drm_crtc_prepare(ctx, crtc) < 0)
|
return -1;
|
|
DRM_DEBUG("CRTC[%d]: request setting new cursor %d (%dx%d)\n",
|
crtc->crtc_id, handle, width, height);
|
|
pthread_mutex_lock(&crtc->mutex);
|
if (crtc->state == ERROR) {
|
pthread_mutex_unlock(&crtc->mutex);
|
DRM_ERROR("CRTC[%d]: failed to set cursor\n", crtc->crtc_id);
|
return -1;
|
}
|
|
/* Update next cursor state and notify the thread */
|
cursor_next = &crtc->cursor_next;
|
|
cursor_next->request |= REQ_SET_CURSOR;
|
cursor_next->fb = 0;
|
cursor_next->handle = handle;
|
cursor_next->width = width;
|
cursor_next->height = height;
|
crtc->state = PENDING;
|
pthread_cond_signal(&crtc->cond);
|
|
while (handle && !crtc->verified && crtc->state != ERROR)
|
pthread_cond_wait(&crtc->cond, &crtc->mutex);
|
|
pthread_mutex_unlock(&crtc->mutex);
|
|
if (crtc->state == ERROR) {
|
DRM_ERROR("CRTC[%d]: failed to set cursor\n", crtc->crtc_id);
|
return -1;
|
}
|
|
return 0;
|
}
|
|
static int drm_move_cursor(int fd, uint32_t crtc_id, int x, int y)
|
{
|
drm_ctx *ctx;
|
drm_crtc *crtc;
|
drm_cursor_state *cursor_next;
|
|
ctx = drm_get_ctx(fd);
|
if (!ctx)
|
return -1;
|
|
if (ctx->hide)
|
return 0;
|
|
crtc = drm_get_crtc(ctx, crtc_id);
|
if (!crtc)
|
return -1;
|
|
if (crtc->state == ERROR || drm_crtc_prepare(ctx, crtc) < 0)
|
return -1;
|
|
if (crtc->width <= 0 || crtc->height <= 0)
|
return -1;
|
|
DRM_DEBUG("CRTC[%d]: request moving cursor to (%d,%d) in (%dx%d)\n",
|
crtc->crtc_id, x, y, crtc->width, crtc->height);
|
|
pthread_mutex_lock(&crtc->mutex);
|
if (crtc->state == ERROR) {
|
pthread_mutex_unlock(&crtc->mutex);
|
return -1;
|
}
|
|
/* Update next cursor state and notify the thread */
|
cursor_next = &crtc->cursor_next;
|
|
cursor_next->request |= REQ_MOVE_CURSOR;
|
cursor_next->fb = 0;
|
cursor_next->x = x;
|
cursor_next->y = y;
|
crtc->state = PENDING;
|
pthread_cond_signal(&crtc->cond);
|
pthread_mutex_unlock(&crtc->mutex);
|
|
return 0;
|
}
|
|
/* Hook functions */
|
|
int drmModeSetCursor(int fd, uint32_t crtcId, uint32_t bo_handle,
|
uint32_t width, uint32_t height)
|
{
|
/* Init log file */
|
drm_get_ctx(fd);
|
|
DRM_DEBUG("fd: %d crtc: %d handle: %d size: %dx%d\n",
|
fd, crtcId, bo_handle, width, height);
|
return drm_set_cursor(fd, crtcId, bo_handle, width, height);
|
}
|
|
int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y)
|
{
|
DRM_DEBUG("fd: %d crtc: %d position: %d,%d\n", fd, crtcId, x, y);
|
return drm_move_cursor(fd, crtcId, x, y);
|
}
|
|
int drmModeSetCursor2(int fd, uint32_t crtcId, uint32_t bo_handle,
|
uint32_t width, uint32_t height,
|
int32_t hot_x, int32_t hot_y)
|
{
|
DRM_DEBUG("fd: %d crtc: %d handle: %d size: %dx%d (%d, %d)\n",
|
fd, crtcId, bo_handle, width, height, hot_x, hot_y);
|
return -EINVAL;
|
}
|