1723 lines
53 KiB
Diff
1723 lines
53 KiB
Diff
From 650c5444273993f969b9cd7df9add6ab2df0414e Mon Sep 17 00:00:00 2001
|
|
From: David Herrmann <dh.herrmann@gmail.com>
|
|
Date: Fri, 19 Sep 2014 14:05:52 +0200
|
|
Subject: [PATCH] terminal: add graphics interface
|
|
|
|
The grdev layer provides graphics-device access via the
|
|
libsystemd-terminal library. It will be used by all terminal helpers to
|
|
actually access display hardware.
|
|
|
|
Like idev, the grdev layer is built around session objects. On each
|
|
session object you add/remove graphics devices as they appear and vanish.
|
|
Any device type can be supported via specific card-backends. The exported
|
|
grdev API hides any device details.
|
|
|
|
Graphics devices are represented by "cards". Those are hidden in the
|
|
session and any pipe-configuration is automatically applied. Out of those,
|
|
we configure displays which are then exported to the API user. Displays
|
|
are meant as lowest hardware entity available outside of grdev. The
|
|
underlying pipe configuration is fully hidden and not accessible from the
|
|
outside. The grdev tiling layer allows almost arbitrary setups out of
|
|
multiple pipes, but so far we only use a small subset of this. More will
|
|
follow.
|
|
|
|
A grdev-display is meant to represent real connected displays/monitors.
|
|
The upper level screen arrangements are user policy and not controlled by
|
|
grdev. Applications are free to apply any policy they want.
|
|
|
|
Real card-backends will follow in later patches.
|
|
---
|
|
Makefile.am | 3 +
|
|
configure.ac | 2 +-
|
|
src/libsystemd-terminal/grdev-internal.h | 237 ++++++
|
|
src/libsystemd-terminal/grdev.c | 1219 ++++++++++++++++++++++++++++++
|
|
src/libsystemd-terminal/grdev.h | 182 +++++
|
|
5 files changed, 1642 insertions(+), 1 deletion(-)
|
|
create mode 100644 src/libsystemd-terminal/grdev-internal.h
|
|
create mode 100644 src/libsystemd-terminal/grdev.c
|
|
create mode 100644 src/libsystemd-terminal/grdev.h
|
|
|
|
diff --git a/Makefile.am b/Makefile.am
|
|
index 5dc17f8fe7..1931c5d96b 100644
|
|
--- a/Makefile.am
|
|
+++ b/Makefile.am
|
|
@@ -3005,6 +3005,9 @@ libsystemd_terminal_la_CFLAGS = \
|
|
$(TERMINAL_CFLAGS)
|
|
|
|
libsystemd_terminal_la_SOURCES = \
|
|
+ src/libsystemd-terminal/grdev.h \
|
|
+ src/libsystemd-terminal/grdev-internal.h \
|
|
+ src/libsystemd-terminal/grdev.c \
|
|
src/libsystemd-terminal/idev.h \
|
|
src/libsystemd-terminal/idev-internal.h \
|
|
src/libsystemd-terminal/idev.c \
|
|
diff --git a/configure.ac b/configure.ac
|
|
index fb169042d8..38a165c101 100644
|
|
--- a/configure.ac
|
|
+++ b/configure.ac
|
|
@@ -1065,7 +1065,7 @@ AM_CONDITIONAL(ENABLE_MULTI_SEAT_X, [test "$have_multi_seat_x" = "yes"])
|
|
have_terminal=no
|
|
AC_ARG_ENABLE(terminal, AS_HELP_STRING([--enable-terminal], [enable terminal support]))
|
|
if test "x$enable_terminal" = "xyes"; then
|
|
- PKG_CHECK_MODULES([TERMINAL], [ libevdev >= 1.2 xkbcommon >= 0.4 ], [have_terminal=yes])
|
|
+ PKG_CHECK_MODULES([TERMINAL], [ libevdev >= 1.2 xkbcommon >= 0.4 libdrm >= 2.4], [have_terminal=yes])
|
|
AS_IF([test "x$have_terminal" != xyes -a "x$enable_terminal" = xyes],
|
|
[AC_MSG_ERROR([*** terminal support requested but required dependencies not available])],
|
|
[test "x$have_terminal" = xyes],
|
|
diff --git a/src/libsystemd-terminal/grdev-internal.h b/src/libsystemd-terminal/grdev-internal.h
|
|
new file mode 100644
|
|
index 0000000000..7e69c49b63
|
|
--- /dev/null
|
|
+++ b/src/libsystemd-terminal/grdev-internal.h
|
|
@@ -0,0 +1,237 @@
|
|
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
+
|
|
+/***
|
|
+ This file is part of systemd.
|
|
+
|
|
+ Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
|
|
+
|
|
+ systemd is free software; you can redistribute it and/or modify it
|
|
+ under the terms of the GNU Lesser General Public License as published by
|
|
+ the Free Software Foundation; either version 2.1 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ systemd 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
|
|
+ Lesser General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU Lesser General Public License
|
|
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
+***/
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include <inttypes.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdlib.h>
|
|
+#include <systemd/sd-bus.h>
|
|
+#include <systemd/sd-event.h>
|
|
+#include "grdev.h"
|
|
+#include "hashmap.h"
|
|
+#include "list.h"
|
|
+#include "util.h"
|
|
+
|
|
+typedef struct grdev_tile grdev_tile;
|
|
+typedef struct grdev_display_cache grdev_display_cache;
|
|
+
|
|
+typedef struct grdev_pipe_vtable grdev_pipe_vtable;
|
|
+typedef struct grdev_pipe grdev_pipe;
|
|
+typedef struct grdev_card_vtable grdev_card_vtable;
|
|
+typedef struct grdev_card grdev_card;
|
|
+
|
|
+/*
|
|
+ * Displays
|
|
+ */
|
|
+
|
|
+enum {
|
|
+ GRDEV_TILE_LEAF,
|
|
+ GRDEV_TILE_NODE,
|
|
+ GRDEV_TILE_CNT
|
|
+};
|
|
+
|
|
+struct grdev_tile {
|
|
+ LIST_FIELDS(grdev_tile, childs_by_node);
|
|
+ grdev_tile *parent;
|
|
+ grdev_display *display;
|
|
+
|
|
+ uint32_t x;
|
|
+ uint32_t y;
|
|
+ unsigned int rotate;
|
|
+ unsigned int flip;
|
|
+ uint32_t cache_w;
|
|
+ uint32_t cache_h;
|
|
+
|
|
+ unsigned int type;
|
|
+
|
|
+ union {
|
|
+ struct {
|
|
+ grdev_pipe *pipe;
|
|
+ } leaf;
|
|
+
|
|
+ struct {
|
|
+ size_t n_childs;
|
|
+ LIST_HEAD(grdev_tile, child_list);
|
|
+ } node;
|
|
+ };
|
|
+};
|
|
+
|
|
+int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe);
|
|
+int grdev_tile_new_node(grdev_tile **out);
|
|
+grdev_tile *grdev_tile_free(grdev_tile *tile);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_tile*, grdev_tile_free);
|
|
+
|
|
+struct grdev_display {
|
|
+ grdev_session *session;
|
|
+ char *name;
|
|
+
|
|
+ size_t n_leafs;
|
|
+ grdev_tile *tile;
|
|
+
|
|
+ size_t n_pipes;
|
|
+ size_t max_pipes;
|
|
+
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+
|
|
+ struct grdev_display_cache {
|
|
+ grdev_pipe *pipe;
|
|
+ grdev_display_target target;
|
|
+
|
|
+ bool incomplete : 1;
|
|
+ } *pipes;
|
|
+
|
|
+ bool enabled : 1;
|
|
+ bool public : 1;
|
|
+ bool modified : 1;
|
|
+ bool framed : 1;
|
|
+};
|
|
+
|
|
+grdev_display *grdev_find_display(grdev_session *session, const char *name);
|
|
+
|
|
+int grdev_display_new(grdev_display **out, grdev_session *session, const char *name);
|
|
+grdev_display *grdev_display_free(grdev_display *display);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_display*, grdev_display_free);
|
|
+
|
|
+/*
|
|
+ * Pipes
|
|
+ */
|
|
+
|
|
+struct grdev_pipe_vtable {
|
|
+ void (*free) (grdev_pipe *pipe);
|
|
+ void (*enable) (grdev_pipe *pipe);
|
|
+ void (*disable) (grdev_pipe *pipe);
|
|
+ grdev_fb *(*target) (grdev_pipe *pipe);
|
|
+};
|
|
+
|
|
+struct grdev_pipe {
|
|
+ const grdev_pipe_vtable *vtable;
|
|
+ grdev_card *card;
|
|
+ char *name;
|
|
+
|
|
+ grdev_tile *tile;
|
|
+ grdev_display_cache *cache;
|
|
+
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+
|
|
+ size_t max_fbs;
|
|
+ grdev_fb *front;
|
|
+ grdev_fb *back;
|
|
+ grdev_fb **fbs;
|
|
+
|
|
+ bool enabled : 1;
|
|
+ bool running : 1;
|
|
+ bool flip : 1;
|
|
+ bool flipping : 1;
|
|
+};
|
|
+
|
|
+#define GRDEV_PIPE_INIT(_vtable, _card) ((grdev_pipe){ \
|
|
+ .vtable = (_vtable), \
|
|
+ .card = (_card), \
|
|
+ })
|
|
+
|
|
+grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name);
|
|
+
|
|
+int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs);
|
|
+grdev_pipe *grdev_pipe_free(grdev_pipe *pipe);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_pipe*, grdev_pipe_free);
|
|
+
|
|
+void grdev_pipe_ready(grdev_pipe *pipe, bool running);
|
|
+void grdev_pipe_frame(grdev_pipe *pipe);
|
|
+
|
|
+/*
|
|
+ * Cards
|
|
+ */
|
|
+
|
|
+struct grdev_card_vtable {
|
|
+ void (*free) (grdev_card *card);
|
|
+ void (*enable) (grdev_card *card);
|
|
+ void (*disable) (grdev_card *card);
|
|
+ void (*commit) (grdev_card *card);
|
|
+ void (*restore) (grdev_card *card);
|
|
+};
|
|
+
|
|
+struct grdev_card {
|
|
+ const grdev_card_vtable *vtable;
|
|
+ grdev_session *session;
|
|
+ char *name;
|
|
+
|
|
+ Hashmap *pipe_map;
|
|
+
|
|
+ bool enabled : 1;
|
|
+ bool modified : 1;
|
|
+};
|
|
+
|
|
+#define GRDEV_CARD_INIT(_vtable, _session) ((grdev_card){ \
|
|
+ .vtable = (_vtable), \
|
|
+ .session = (_session), \
|
|
+ })
|
|
+
|
|
+grdev_card *grdev_find_card(grdev_session *session, const char *name);
|
|
+
|
|
+int grdev_card_add(grdev_card *card, const char *name);
|
|
+grdev_card *grdev_card_free(grdev_card *card);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_card*, grdev_card_free);
|
|
+
|
|
+/*
|
|
+ * Sessions
|
|
+ */
|
|
+
|
|
+struct grdev_session {
|
|
+ grdev_context *context;
|
|
+ char *name;
|
|
+ char *path;
|
|
+ grdev_event_fn event_fn;
|
|
+ void *userdata;
|
|
+
|
|
+ unsigned long n_pins;
|
|
+
|
|
+ Hashmap *card_map;
|
|
+ Hashmap *display_map;
|
|
+
|
|
+ bool custom : 1;
|
|
+ bool managed : 1;
|
|
+ bool enabled : 1;
|
|
+ bool modified : 1;
|
|
+};
|
|
+
|
|
+grdev_session *grdev_session_pin(grdev_session *session);
|
|
+grdev_session *grdev_session_unpin(grdev_session *session);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_unpin);
|
|
+
|
|
+/*
|
|
+ * Contexts
|
|
+ */
|
|
+
|
|
+struct grdev_context {
|
|
+ unsigned long ref;
|
|
+ sd_event *event;
|
|
+ sd_bus *sysbus;
|
|
+
|
|
+ Hashmap *session_map;
|
|
+};
|
|
diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c
|
|
new file mode 100644
|
|
index 0000000000..ab1c407ecb
|
|
--- /dev/null
|
|
+++ b/src/libsystemd-terminal/grdev.c
|
|
@@ -0,0 +1,1219 @@
|
|
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
+
|
|
+/***
|
|
+ This file is part of systemd.
|
|
+
|
|
+ Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
|
|
+
|
|
+ systemd is free software; you can redistribute it and/or modify it
|
|
+ under the terms of the GNU Lesser General Public License as published by
|
|
+ the Free Software Foundation; either version 2.1 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ systemd 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
|
|
+ Lesser General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU Lesser General Public License
|
|
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
+***/
|
|
+
|
|
+#include <inttypes.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdlib.h>
|
|
+#include <systemd/sd-bus.h>
|
|
+#include <systemd/sd-event.h>
|
|
+#include <systemd/sd-login.h>
|
|
+#include "grdev.h"
|
|
+#include "grdev-internal.h"
|
|
+#include "hashmap.h"
|
|
+#include "login-shared.h"
|
|
+#include "macro.h"
|
|
+#include "util.h"
|
|
+
|
|
+static void pipe_enable(grdev_pipe *pipe);
|
|
+static void pipe_disable(grdev_pipe *pipe);
|
|
+static void card_modified(grdev_card *card);
|
|
+static void session_frame(grdev_session *session, grdev_display *display);
|
|
+
|
|
+/*
|
|
+ * Displays
|
|
+ */
|
|
+
|
|
+static inline grdev_tile *tile_leftmost(grdev_tile *tile) {
|
|
+ if (!tile)
|
|
+ return NULL;
|
|
+
|
|
+ while (tile->type == GRDEV_TILE_NODE && tile->node.child_list)
|
|
+ tile = tile->node.child_list;
|
|
+
|
|
+ return tile;
|
|
+}
|
|
+
|
|
+#define TILE_FOREACH(_root, _i) \
|
|
+ for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->childs_by_node_next) ? : _i->parent)
|
|
+
|
|
+#define TILE_FOREACH_SAFE(_root, _i, _next) \
|
|
+ for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->childs_by_node_next) ? : _i->parent), true); _i = _next)
|
|
+
|
|
+static void tile_link(grdev_tile *tile, grdev_tile *parent) {
|
|
+ grdev_display *display;
|
|
+ grdev_tile *t;
|
|
+
|
|
+ assert(tile);
|
|
+ assert(!tile->parent);
|
|
+ assert(!tile->display);
|
|
+ assert(parent);
|
|
+ assert(parent->type == GRDEV_TILE_NODE);
|
|
+
|
|
+ display = parent->display;
|
|
+
|
|
+ assert(!display || !display->enabled);
|
|
+
|
|
+ ++parent->node.n_childs;
|
|
+ LIST_PREPEND(childs_by_node, parent->node.child_list, tile);
|
|
+ tile->parent = parent;
|
|
+
|
|
+ if (display) {
|
|
+ display->modified = true;
|
|
+ TILE_FOREACH(tile, t) {
|
|
+ t->display = display;
|
|
+ if (t->type == GRDEV_TILE_LEAF) {
|
|
+ ++display->n_leafs;
|
|
+ if (display->enabled)
|
|
+ pipe_enable(t->leaf.pipe);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void tile_unlink(grdev_tile *tile) {
|
|
+ grdev_tile *parent, *t;
|
|
+ grdev_display *display;
|
|
+
|
|
+ assert(tile);
|
|
+
|
|
+ display = tile->display;
|
|
+ parent = tile->parent;
|
|
+ if (!parent) {
|
|
+ assert(!display);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ assert(parent->type == GRDEV_TILE_NODE);
|
|
+ assert(parent->display == display);
|
|
+ assert(parent->node.n_childs > 0);
|
|
+
|
|
+ --parent->node.n_childs;
|
|
+ LIST_REMOVE(childs_by_node, parent->node.child_list, tile);
|
|
+ tile->parent = NULL;
|
|
+
|
|
+ if (display) {
|
|
+ display->modified = true;
|
|
+ TILE_FOREACH(tile, t) {
|
|
+ t->display = NULL;
|
|
+ if (t->type == GRDEV_TILE_LEAF) {
|
|
+ --display->n_leafs;
|
|
+ t->leaf.pipe->cache = NULL;
|
|
+ pipe_disable(t->leaf.pipe);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Tile trees are driven by leafs. Internal nodes have no owner, thus,
|
|
+ * we must take care to not leave them around. Therefore, whenever we
|
|
+ * unlink any part of a tree, we also destroy the parent, in case it's
|
|
+ * now stale.
|
|
+ * Parents are stale if they have no childs and either have no display
|
|
+ * or if they are intermediate nodes (i.e, they have a parent).
|
|
+ * This means, you can easily create trees, but you can never partially
|
|
+ * move or destruct them so far. They're always reduced to minimal form
|
|
+ * if you cut them. This might change later, but so far we didn't need
|
|
+ * partial destruction or the ability to move whole trees. */
|
|
+
|
|
+ if (parent->node.n_childs < 1 && (parent->parent || !parent->display))
|
|
+ grdev_tile_free(parent);
|
|
+}
|
|
+
|
|
+static int tile_new(grdev_tile **out) {
|
|
+ _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
|
|
+
|
|
+ assert(out);
|
|
+
|
|
+ tile = new0(grdev_tile, 1);
|
|
+ if (!tile)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ tile->type = (unsigned)-1;
|
|
+
|
|
+ *out = tile;
|
|
+ tile = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) {
|
|
+ _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
|
|
+ int r;
|
|
+
|
|
+ assert_return(pipe, -EINVAL);
|
|
+ assert_return(!pipe->tile, -EINVAL);
|
|
+
|
|
+ r = tile_new(&tile);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ tile->type = GRDEV_TILE_LEAF;
|
|
+ tile->leaf.pipe = pipe;
|
|
+
|
|
+ if (out)
|
|
+ *out = tile;
|
|
+ tile = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int grdev_tile_new_node(grdev_tile **out) {
|
|
+ _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
|
|
+ int r;
|
|
+
|
|
+ assert_return(out, -EINVAL);
|
|
+
|
|
+ r = tile_new(&tile);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ tile->type = GRDEV_TILE_NODE;
|
|
+
|
|
+ *out = tile;
|
|
+ tile = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+grdev_tile *grdev_tile_free(grdev_tile *tile) {
|
|
+ if (!tile)
|
|
+ return NULL;
|
|
+
|
|
+ tile_unlink(tile);
|
|
+
|
|
+ switch (tile->type) {
|
|
+ case GRDEV_TILE_LEAF:
|
|
+ assert(!tile->parent);
|
|
+ assert(!tile->display);
|
|
+ assert(tile->leaf.pipe);
|
|
+
|
|
+ break;
|
|
+ case GRDEV_TILE_NODE:
|
|
+ assert(!tile->parent);
|
|
+ assert(!tile->display);
|
|
+ assert(tile->node.n_childs == 0);
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ free(tile);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+grdev_display *grdev_find_display(grdev_session *session, const char *name) {
|
|
+ assert_return(session, NULL);
|
|
+ assert_return(name, NULL);
|
|
+
|
|
+ return hashmap_get(session->display_map, name);
|
|
+}
|
|
+
|
|
+int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) {
|
|
+ _cleanup_(grdev_display_freep) grdev_display *display = NULL;
|
|
+ int r;
|
|
+
|
|
+ assert(session);
|
|
+ assert(name);
|
|
+
|
|
+ display = new0(grdev_display, 1);
|
|
+ if (!display)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ display->session = session;
|
|
+
|
|
+ display->name = strdup(name);
|
|
+ if (!display->name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ r = grdev_tile_new_node(&display->tile);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ display->tile->display = display;
|
|
+
|
|
+ r = hashmap_put(session->display_map, display->name, display);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ if (out)
|
|
+ *out = display;
|
|
+ display = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+grdev_display *grdev_display_free(grdev_display *display) {
|
|
+ if (!display)
|
|
+ return NULL;
|
|
+
|
|
+ assert(!display->public);
|
|
+ assert(!display->enabled);
|
|
+ assert(!display->modified);
|
|
+ assert(display->n_leafs == 0);
|
|
+ assert(display->n_pipes == 0);
|
|
+
|
|
+ if (display->name)
|
|
+ hashmap_remove_value(display->session->display_map, display->name, display);
|
|
+
|
|
+ if (display->tile) {
|
|
+ display->tile->display = NULL;
|
|
+ grdev_tile_free(display->tile);
|
|
+ }
|
|
+
|
|
+ free(display->pipes);
|
|
+ free(display->name);
|
|
+ free(display);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+bool grdev_display_is_enabled(grdev_display *display) {
|
|
+ return display && display->enabled;
|
|
+}
|
|
+
|
|
+void grdev_display_enable(grdev_display *display) {
|
|
+ grdev_tile *t;
|
|
+
|
|
+ assert(display);
|
|
+
|
|
+ if (!display->enabled) {
|
|
+ display->enabled = true;
|
|
+ TILE_FOREACH(display->tile, t)
|
|
+ if (t->type == GRDEV_TILE_LEAF)
|
|
+ pipe_enable(t->leaf.pipe);
|
|
+ }
|
|
+}
|
|
+
|
|
+void grdev_display_disable(grdev_display *display) {
|
|
+ grdev_tile *t;
|
|
+
|
|
+ assert(display);
|
|
+
|
|
+ if (display->enabled) {
|
|
+ display->enabled = false;
|
|
+ TILE_FOREACH(display->tile, t)
|
|
+ if (t->type == GRDEV_TILE_LEAF)
|
|
+ pipe_disable(t->leaf.pipe);
|
|
+ }
|
|
+}
|
|
+
|
|
+const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage) {
|
|
+ grdev_display_cache *cache;
|
|
+ size_t idx;
|
|
+
|
|
+ assert_return(display, NULL);
|
|
+ assert_return(!display->modified, NULL);
|
|
+ assert_return(display->enabled, NULL);
|
|
+
|
|
+ if (prev) {
|
|
+ cache = container_of(prev, grdev_display_cache, target);
|
|
+
|
|
+ assert(cache->pipe);
|
|
+ assert(cache->pipe->tile->display == display);
|
|
+ assert(display->pipes >= cache);
|
|
+
|
|
+ idx = (cache - display->pipes) / sizeof(*cache) + 1;
|
|
+ } else {
|
|
+ idx = 0;
|
|
+ }
|
|
+
|
|
+ for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) {
|
|
+ grdev_display_target *target;
|
|
+ grdev_pipe *pipe;
|
|
+ grdev_fb *fb;
|
|
+
|
|
+ pipe = cache->pipe;
|
|
+ target = &cache->target;
|
|
+
|
|
+ if (!pipe->running || !pipe->enabled)
|
|
+ continue;
|
|
+
|
|
+ /* if front-buffer is up-to-date, there's nothing to do */
|
|
+ if (minage > 0 && pipe->front && pipe->front->age >= minage)
|
|
+ continue;
|
|
+
|
|
+ /* find suitable back-buffer */
|
|
+ if (!(fb = pipe->back)) {
|
|
+ if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* if back-buffer is up-to-date, schedule flip */
|
|
+ if (minage > 0 && fb->age >= minage) {
|
|
+ grdev_display_flip_target(display, target, fb->age);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* we have an out-of-date back-buffer; return for redraw */
|
|
+ target->fb = fb;
|
|
+ return target;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age) {
|
|
+ grdev_display_cache *cache;
|
|
+ size_t i;
|
|
+
|
|
+ assert(display);
|
|
+ assert(!display->modified);
|
|
+ assert(display->enabled);
|
|
+ assert(target);
|
|
+ assert(target->fb);
|
|
+
|
|
+ cache = container_of(target, grdev_display_cache, target);
|
|
+
|
|
+ assert(cache->pipe);
|
|
+ assert(cache->pipe->tile->display == display);
|
|
+
|
|
+ /* reset age of all FB on overflow */
|
|
+ if (age < target->fb->age)
|
|
+ for (i = 0; i < cache->pipe->max_fbs; ++i)
|
|
+ if (cache->pipe->fbs[i])
|
|
+ cache->pipe->fbs[i]->age = 0;
|
|
+
|
|
+ ((grdev_fb*)target->fb)->age = age;
|
|
+ cache->pipe->flip = true;
|
|
+}
|
|
+
|
|
+static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) {
|
|
+ uint32_t x, y, width, height;
|
|
+ grdev_display_target *t;
|
|
+
|
|
+ assert(c);
|
|
+ assert(l);
|
|
+ assert(l->cache_w >= c->target.width + c->target.x);
|
|
+ assert(l->cache_h >= c->target.height + c->target.y);
|
|
+
|
|
+ t = &c->target;
|
|
+
|
|
+ /* rotate child */
|
|
+
|
|
+ t->rotate = (t->rotate + l->rotate) & 0x3;
|
|
+
|
|
+ x = t->x;
|
|
+ y = t->y;
|
|
+ width = t->width;
|
|
+ height = t->height;
|
|
+
|
|
+ switch (l->rotate) {
|
|
+ case GRDEV_ROTATE_0:
|
|
+ break;
|
|
+ case GRDEV_ROTATE_90:
|
|
+ t->x = l->cache_h - (height + y);
|
|
+ t->y = x;
|
|
+ t->width = height;
|
|
+ t->height = width;
|
|
+ break;
|
|
+ case GRDEV_ROTATE_180:
|
|
+ t->x = l->cache_w - (width + x);
|
|
+ t->y = l->cache_h - (height + y);
|
|
+ break;
|
|
+ case GRDEV_ROTATE_270:
|
|
+ t->x = y;
|
|
+ t->y = l->cache_w - (width + x);
|
|
+ t->width = height;
|
|
+ t->height = width;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* flip child */
|
|
+
|
|
+ t->flip ^= l->flip;
|
|
+
|
|
+ if (l->flip & GRDEV_FLIP_HORIZONTAL)
|
|
+ t->x = l->cache_w - (t->width + t->x);
|
|
+ if (l->flip & GRDEV_FLIP_VERTICAL)
|
|
+ t->y = l->cache_h - (t->height + t->y);
|
|
+
|
|
+ /* move child */
|
|
+
|
|
+ t->x += l->x;
|
|
+ t->y += l->y;
|
|
+}
|
|
+
|
|
+static void display_cache_targets(grdev_display *display) {
|
|
+ grdev_display_cache *c;
|
|
+ grdev_tile *tile;
|
|
+
|
|
+ assert(display);
|
|
+
|
|
+ /* depth-first with childs before parent */
|
|
+ for (tile = tile_leftmost(display->tile);
|
|
+ tile;
|
|
+ tile = tile_leftmost(tile->childs_by_node_next) ? : tile->parent) {
|
|
+ if (tile->type == GRDEV_TILE_LEAF) {
|
|
+ grdev_pipe *p;
|
|
+
|
|
+ /* We're at a leaf and no parent has been cached, yet.
|
|
+ * Copy the pipe information into the target cache and
|
|
+ * update our global pipe-caches if required. */
|
|
+
|
|
+ assert(tile->leaf.pipe);
|
|
+ assert(display->n_pipes + 1 <= display->max_pipes);
|
|
+
|
|
+ p = tile->leaf.pipe;
|
|
+ c = &display->pipes[display->n_pipes++];
|
|
+
|
|
+ zero(*c);
|
|
+ c->pipe = p;
|
|
+ c->pipe->cache = c;
|
|
+ c->target.width = p->width;
|
|
+ c->target.height = p->height;
|
|
+ tile->cache_w = p->width;
|
|
+ tile->cache_h = p->height;
|
|
+
|
|
+ /* all new tiles are incomplete due to geometry changes */
|
|
+ c->incomplete = true;
|
|
+
|
|
+ display_cache_apply(c, tile);
|
|
+ } else {
|
|
+ grdev_tile *child, *l;
|
|
+
|
|
+ /* We're now at a node with all it's childs already
|
|
+ * computed (depth-first, child before parent). We
|
|
+ * first need to know the size of our tile, then we
|
|
+ * recurse into all leafs and update their cache. */
|
|
+
|
|
+ tile->cache_w = 0;
|
|
+ tile->cache_h = 0;
|
|
+
|
|
+ LIST_FOREACH(childs_by_node, child, tile->node.child_list) {
|
|
+ if (child->x + child->cache_w > tile->cache_w)
|
|
+ tile->cache_w = child->x + child->cache_w;
|
|
+ if (child->y + child->cache_h > tile->cache_h)
|
|
+ tile->cache_h = child->y + child->cache_h;
|
|
+ }
|
|
+
|
|
+ assert(tile->cache_w > 0);
|
|
+ assert(tile->cache_h > 0);
|
|
+
|
|
+ TILE_FOREACH(tile, l)
|
|
+ if (l->type == GRDEV_TILE_LEAF)
|
|
+ display_cache_apply(l->leaf.pipe->cache, tile);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool display_cache(grdev_display *display) {
|
|
+ grdev_tile *tile;
|
|
+ size_t n;
|
|
+ void *t;
|
|
+ int r;
|
|
+
|
|
+ assert(display);
|
|
+
|
|
+ if (!display->modified)
|
|
+ return false;
|
|
+
|
|
+ display->modified = false;
|
|
+ display->framed = false;
|
|
+ display->n_pipes = 0;
|
|
+ display->width = 0;
|
|
+ display->height = 0;
|
|
+
|
|
+ if (display->n_leafs < 1)
|
|
+ return false;
|
|
+
|
|
+ TILE_FOREACH(display->tile, tile)
|
|
+ if (tile->type == GRDEV_TILE_LEAF)
|
|
+ tile->leaf.pipe->cache = NULL;
|
|
+
|
|
+ if (display->n_leafs > display->max_pipes) {
|
|
+ n = ALIGN_POWER2(display->n_leafs);
|
|
+ if (!n) {
|
|
+ r = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ t = realloc_multiply(display->pipes, sizeof(*display->pipes), n);
|
|
+ if (!t) {
|
|
+ r = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ display->pipes = t;
|
|
+ display->max_pipes = n;
|
|
+ }
|
|
+
|
|
+ display_cache_targets(display);
|
|
+
|
|
+ r = 0;
|
|
+
|
|
+out:
|
|
+ if (r < 0)
|
|
+ log_debug("grdev: %s/%s: cannot cache pipes: %s",
|
|
+ display->session->name, display->name, strerror(-r));
|
|
+ return true;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Pipes
|
|
+ */
|
|
+
|
|
+grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) {
|
|
+ assert_return(card, NULL);
|
|
+ assert_return(name, NULL);
|
|
+
|
|
+ return hashmap_get(card->pipe_map, name);
|
|
+}
|
|
+
|
|
+int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) {
|
|
+ int r;
|
|
+
|
|
+ assert_return(pipe, -EINVAL);
|
|
+ assert_return(pipe->vtable, -EINVAL);
|
|
+ assert_return(pipe->vtable->free, -EINVAL);
|
|
+ assert_return(pipe->card, -EINVAL);
|
|
+ assert_return(pipe->card->session, -EINVAL);
|
|
+ assert_return(!pipe->cache, -EINVAL);
|
|
+ assert_return(pipe->width > 0, -EINVAL);
|
|
+ assert_return(pipe->height > 0, -EINVAL);
|
|
+ assert_return(!pipe->enabled, -EINVAL);
|
|
+ assert_return(!pipe->running, -EINVAL);
|
|
+ assert_return(name, -EINVAL);
|
|
+
|
|
+ pipe->name = strdup(name);
|
|
+ if (!pipe->name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (n_fbs > 0) {
|
|
+ pipe->fbs = new0(grdev_fb*, n_fbs);
|
|
+ if (!pipe->fbs)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ pipe->max_fbs = n_fbs;
|
|
+ }
|
|
+
|
|
+ r = grdev_tile_new_leaf(&pipe->tile, pipe);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ card_modified(pipe->card);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) {
|
|
+ grdev_pipe tmp;
|
|
+
|
|
+ if (!pipe)
|
|
+ return NULL;
|
|
+
|
|
+ assert(pipe->card);
|
|
+ assert(pipe->vtable);
|
|
+ assert(pipe->vtable->free);
|
|
+
|
|
+ if (pipe->name)
|
|
+ hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe);
|
|
+ if (pipe->tile)
|
|
+ tile_unlink(pipe->tile);
|
|
+
|
|
+ assert(!pipe->cache);
|
|
+
|
|
+ tmp = *pipe;
|
|
+ pipe->vtable->free(pipe);
|
|
+
|
|
+ grdev_tile_free(tmp.tile);
|
|
+ card_modified(tmp.card);
|
|
+ free(tmp.fbs);
|
|
+ free(tmp.name);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void pipe_enable(grdev_pipe *pipe) {
|
|
+ assert(pipe);
|
|
+
|
|
+ if (!pipe->enabled) {
|
|
+ pipe->enabled = true;
|
|
+ if (pipe->vtable->enable)
|
|
+ pipe->vtable->enable(pipe);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void pipe_disable(grdev_pipe *pipe) {
|
|
+ assert(pipe);
|
|
+
|
|
+ if (pipe->enabled) {
|
|
+ pipe->enabled = false;
|
|
+ if (pipe->vtable->disable)
|
|
+ pipe->vtable->disable(pipe);
|
|
+ }
|
|
+}
|
|
+
|
|
+void grdev_pipe_ready(grdev_pipe *pipe, bool running) {
|
|
+ assert(pipe);
|
|
+
|
|
+ /* grdev_pipe_ready() is used by backends to notify about pipe state
|
|
+ * changed. If a pipe is ready, it can be fully used by us (available,
|
|
+ * enabled and accessable). Backends can disable pipes at any time
|
|
+ * (like for async revocation), but can only enable them from parent
|
|
+ * context. Otherwise, we might call user-callbacks recursively. */
|
|
+
|
|
+ if (pipe->running == running)
|
|
+ return;
|
|
+
|
|
+ pipe->running = running;
|
|
+
|
|
+ /* runtime events for unused pipes are not interesting */
|
|
+ if (pipe->cache) {
|
|
+ grdev_display *display = pipe->tile->display;
|
|
+
|
|
+ assert(display);
|
|
+
|
|
+ if (running) {
|
|
+ if (pipe->enabled)
|
|
+ session_frame(display->session, display);
|
|
+ } else {
|
|
+ pipe->cache->incomplete = true;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void grdev_pipe_frame(grdev_pipe *pipe) {
|
|
+ grdev_display *display;
|
|
+
|
|
+ assert(pipe);
|
|
+
|
|
+ /* if pipe is unused, ignore any frame events */
|
|
+ if (!pipe->cache)
|
|
+ return;
|
|
+
|
|
+ display = pipe->tile->display;
|
|
+ assert(display);
|
|
+
|
|
+ if (pipe->enabled)
|
|
+ session_frame(display->session, display);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Cards
|
|
+ */
|
|
+
|
|
+grdev_card *grdev_find_card(grdev_session *session, const char *name) {
|
|
+ assert_return(session, NULL);
|
|
+ assert_return(name, NULL);
|
|
+
|
|
+ return hashmap_get(session->card_map, name);
|
|
+}
|
|
+
|
|
+int grdev_card_add(grdev_card *card, const char *name) {
|
|
+ int r;
|
|
+
|
|
+ assert_return(card, -EINVAL);
|
|
+ assert_return(card->vtable, -EINVAL);
|
|
+ assert_return(card->vtable->free, -EINVAL);
|
|
+ assert_return(card->session, -EINVAL);
|
|
+ assert_return(name, -EINVAL);
|
|
+
|
|
+ card->name = strdup(name);
|
|
+ if (!card->name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ card->pipe_map = hashmap_new(&string_hash_ops);
|
|
+ if (!card->pipe_map)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ r = hashmap_put(card->session->card_map, card->name, card);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+grdev_card *grdev_card_free(grdev_card *card) {
|
|
+ grdev_card tmp;
|
|
+
|
|
+ if (!card)
|
|
+ return NULL;
|
|
+
|
|
+ assert(!card->enabled);
|
|
+ assert(card->vtable);
|
|
+ assert(card->vtable->free);
|
|
+
|
|
+ if (card->name)
|
|
+ hashmap_remove_value(card->session->card_map, card->name, card);
|
|
+
|
|
+ tmp = *card;
|
|
+ card->vtable->free(card);
|
|
+
|
|
+ assert(hashmap_size(tmp.pipe_map) == 0);
|
|
+
|
|
+ hashmap_free(tmp.pipe_map);
|
|
+ free(tmp.name);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void card_modified(grdev_card *card) {
|
|
+ assert(card);
|
|
+ assert(card->session->n_pins > 0);
|
|
+
|
|
+ card->modified = true;
|
|
+}
|
|
+
|
|
+static void grdev_card_enable(grdev_card *card) {
|
|
+ assert(card);
|
|
+
|
|
+ if (!card->enabled) {
|
|
+ card->enabled = true;
|
|
+ if (card->vtable->enable)
|
|
+ card->vtable->enable(card);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void grdev_card_disable(grdev_card *card) {
|
|
+ assert(card);
|
|
+
|
|
+ if (card->enabled) {
|
|
+ card->enabled = false;
|
|
+ if (card->vtable->disable)
|
|
+ card->vtable->disable(card);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Sessions
|
|
+ */
|
|
+
|
|
+static void session_raise(grdev_session *session, grdev_event *event) {
|
|
+ session->event_fn(session, session->userdata, event);
|
|
+}
|
|
+
|
|
+static void session_raise_display_add(grdev_session *session, grdev_display *display) {
|
|
+ grdev_event event = {
|
|
+ .type = GRDEV_EVENT_DISPLAY_ADD,
|
|
+ .display_add = {
|
|
+ .display = display,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ session_raise(session, &event);
|
|
+}
|
|
+
|
|
+static void session_raise_display_remove(grdev_session *session, grdev_display *display) {
|
|
+ grdev_event event = {
|
|
+ .type = GRDEV_EVENT_DISPLAY_REMOVE,
|
|
+ .display_remove = {
|
|
+ .display = display,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ session_raise(session, &event);
|
|
+}
|
|
+
|
|
+static void session_raise_display_change(grdev_session *session, grdev_display *display) {
|
|
+ grdev_event event = {
|
|
+ .type = GRDEV_EVENT_DISPLAY_CHANGE,
|
|
+ .display_change = {
|
|
+ .display = display,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ session_raise(session, &event);
|
|
+}
|
|
+
|
|
+static void session_raise_display_frame(grdev_session *session, grdev_display *display) {
|
|
+ grdev_event event = {
|
|
+ .type = GRDEV_EVENT_DISPLAY_FRAME,
|
|
+ .display_frame = {
|
|
+ .display = display,
|
|
+ },
|
|
+ };
|
|
+
|
|
+ session_raise(session, &event);
|
|
+}
|
|
+
|
|
+static void session_add_card(grdev_session *session, grdev_card *card) {
|
|
+ assert(session);
|
|
+ assert(card);
|
|
+
|
|
+ log_debug("grdev: %s: add card '%s'", session->name, card->name);
|
|
+
|
|
+ /* Cards are not exposed to users, but managed internally. Cards are
|
|
+ * enabled if the session is enabled, and will track that state. The
|
|
+ * backend can probe the card at any time, but only if enabled. It
|
|
+ * will then add pipes according to hardware state.
|
|
+ * That is, the card may create pipes as soon as we enable it here. */
|
|
+
|
|
+ if (session->enabled)
|
|
+ grdev_card_enable(card);
|
|
+}
|
|
+
|
|
+static void session_remove_card(grdev_session *session, grdev_card *card) {
|
|
+ assert(session);
|
|
+ assert(card);
|
|
+
|
|
+ log_debug("grdev: %s: remove card '%s'", session->name, card->name);
|
|
+
|
|
+ /* As cards are not exposed, it can never be accessed by outside
|
|
+ * users and we can simply remove it. Disabling the card does not
|
|
+ * necessarily drop all pipes of the card. This is usually deferred
|
|
+ * to card destruction (as pipes are cached as long as FDs remain
|
|
+ * open). Therefore, the card destruction might cause pipes, and thus
|
|
+ * visible displays, to be removed. */
|
|
+
|
|
+ grdev_card_disable(card);
|
|
+ grdev_card_free(card);
|
|
+}
|
|
+
|
|
+static void session_add_display(grdev_session *session, grdev_display *display) {
|
|
+ assert(session);
|
|
+ assert(display);
|
|
+ assert(!display->enabled);
|
|
+
|
|
+ log_debug("grdev: %s: add display '%s'", session->name, display->name);
|
|
+
|
|
+ /* Displays are the main entity for public API users. We create them
|
|
+ * independent of card backends and they wrap any underlying display
|
|
+ * architecture. Displays are public at all times, thus, may be entered
|
|
+ * by outside users at any time. */
|
|
+
|
|
+ display->public = true;
|
|
+ session_raise_display_add(session, display);
|
|
+}
|
|
+
|
|
+static void session_remove_display(grdev_session *session, grdev_display *display) {
|
|
+ assert(session);
|
|
+ assert(display);
|
|
+
|
|
+ log_debug("grdev: %s: remove display '%s'", session->name, display->name);
|
|
+
|
|
+ /* Displays are public, so we have to be careful when removing them.
|
|
+ * We first tell users about their removal, disable them and then drop
|
|
+ * them. We now, after the notification, no external access will
|
|
+ * happen. Therefore, we can release the tiles afterwards safely. */
|
|
+
|
|
+ if (display->public) {
|
|
+ display->public = false;
|
|
+ session_raise_display_remove(session, display);
|
|
+ }
|
|
+
|
|
+ grdev_display_disable(display);
|
|
+ grdev_display_free(display);
|
|
+}
|
|
+
|
|
+static void session_change_display(grdev_session *session, grdev_display *display) {
|
|
+ bool changed;
|
|
+
|
|
+ assert(session);
|
|
+ assert(display);
|
|
+
|
|
+ changed = display_cache(display);
|
|
+
|
|
+ if (display->n_leafs == 0)
|
|
+ session_remove_display(session, display);
|
|
+ else if (!display->public)
|
|
+ session_add_display(session, display);
|
|
+ else if (changed)
|
|
+ session_raise_display_change(session, display);
|
|
+ else if (display->framed)
|
|
+ session_frame(session, display);
|
|
+}
|
|
+
|
|
+static void session_frame(grdev_session *session, grdev_display *display) {
|
|
+ assert(session);
|
|
+ assert(display);
|
|
+
|
|
+ display->framed = false;
|
|
+
|
|
+ if (!display->enabled || !session->enabled)
|
|
+ return;
|
|
+
|
|
+ if (session->n_pins > 0)
|
|
+ display->framed = true;
|
|
+ else
|
|
+ session_raise_display_frame(session, display);
|
|
+}
|
|
+
|
|
+int grdev_session_new(grdev_session **out,
|
|
+ grdev_context *context,
|
|
+ unsigned int flags,
|
|
+ const char *name,
|
|
+ grdev_event_fn event_fn,
|
|
+ void *userdata) {
|
|
+ _cleanup_(grdev_session_freep) grdev_session *session = NULL;
|
|
+ int r;
|
|
+
|
|
+ assert(out);
|
|
+ assert(context);
|
|
+ assert(name);
|
|
+ assert(event_fn);
|
|
+ assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL);
|
|
+ assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL);
|
|
+ assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL);
|
|
+
|
|
+ session = new0(grdev_session, 1);
|
|
+ if (!session)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ session->context = grdev_context_ref(context);
|
|
+ session->custom = flags & GRDEV_SESSION_CUSTOM;
|
|
+ session->managed = flags & GRDEV_SESSION_MANAGED;
|
|
+ session->event_fn = event_fn;
|
|
+ session->userdata = userdata;
|
|
+
|
|
+ session->name = strdup(name);
|
|
+ if (!session->name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (session->managed) {
|
|
+ r = sd_bus_path_encode("/org/freedesktop/login1/session",
|
|
+ session->name, &session->path);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ session->card_map = hashmap_new(&string_hash_ops);
|
|
+ if (!session->card_map)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ session->display_map = hashmap_new(&string_hash_ops);
|
|
+ if (!session->display_map)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ r = hashmap_put(context->session_map, session->name, session);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ *out = session;
|
|
+ session = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+grdev_session *grdev_session_free(grdev_session *session) {
|
|
+ grdev_card *card;
|
|
+
|
|
+ if (!session)
|
|
+ return NULL;
|
|
+
|
|
+ grdev_session_disable(session);
|
|
+
|
|
+ while ((card = hashmap_first(session->card_map)))
|
|
+ session_remove_card(session, card);
|
|
+
|
|
+ assert(hashmap_size(session->display_map) == 0);
|
|
+
|
|
+ if (session->name)
|
|
+ hashmap_remove_value(session->context->session_map, session->name, session);
|
|
+
|
|
+ hashmap_free(session->display_map);
|
|
+ hashmap_free(session->card_map);
|
|
+ session->context = grdev_context_unref(session->context);
|
|
+ free(session->path);
|
|
+ free(session->name);
|
|
+ free(session);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+bool grdev_session_is_enabled(grdev_session *session) {
|
|
+ return session && session->enabled;
|
|
+}
|
|
+
|
|
+void grdev_session_enable(grdev_session *session) {
|
|
+ grdev_card *card;
|
|
+ Iterator iter;
|
|
+
|
|
+ assert(session);
|
|
+
|
|
+ if (!session->enabled) {
|
|
+ session->enabled = true;
|
|
+ HASHMAP_FOREACH(card, session->card_map, iter)
|
|
+ grdev_card_enable(card);
|
|
+ }
|
|
+}
|
|
+
|
|
+void grdev_session_disable(grdev_session *session) {
|
|
+ grdev_card *card;
|
|
+ Iterator iter;
|
|
+
|
|
+ assert(session);
|
|
+
|
|
+ if (session->enabled) {
|
|
+ session->enabled = false;
|
|
+ HASHMAP_FOREACH(card, session->card_map, iter)
|
|
+ grdev_card_disable(card);
|
|
+ }
|
|
+}
|
|
+
|
|
+void grdev_session_commit(grdev_session *session) {
|
|
+ grdev_card *card;
|
|
+ Iterator iter;
|
|
+
|
|
+ assert(session);
|
|
+
|
|
+ if (!session->enabled)
|
|
+ return;
|
|
+
|
|
+ HASHMAP_FOREACH(card, session->card_map, iter)
|
|
+ if (card->vtable->commit)
|
|
+ card->vtable->commit(card);
|
|
+}
|
|
+
|
|
+void grdev_session_restore(grdev_session *session) {
|
|
+ grdev_card *card;
|
|
+ Iterator iter;
|
|
+
|
|
+ assert(session);
|
|
+
|
|
+ if (!session->enabled)
|
|
+ return;
|
|
+
|
|
+ HASHMAP_FOREACH(card, session->card_map, iter)
|
|
+ if (card->vtable->restore)
|
|
+ card->vtable->restore(card);
|
|
+}
|
|
+
|
|
+static void session_configure(grdev_session *session) {
|
|
+ grdev_display *display;
|
|
+ grdev_tile *tile;
|
|
+ grdev_card *card;
|
|
+ grdev_pipe *pipe;
|
|
+ Iterator i, j;
|
|
+ int r;
|
|
+
|
|
+ assert(session);
|
|
+
|
|
+ /*
|
|
+ * Whenever backends add or remove pipes, we set session->modified and
|
|
+ * require them to pin the session while modifying it. On release, we
|
|
+ * reconfigure the device and re-assign displays to all modified pipes.
|
|
+ *
|
|
+ * So far, we configure each pipe as a separate display. We do not
|
|
+ * support user-configuration, nor have we gotten any reports from
|
|
+ * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
|
|
+ * we get reports, we keep the logic to a minimum.
|
|
+ */
|
|
+
|
|
+ /* create new displays for all unconfigured pipes */
|
|
+ HASHMAP_FOREACH(card, session->card_map, i) {
|
|
+ if (!card->modified)
|
|
+ continue;
|
|
+
|
|
+ card->modified = false;
|
|
+
|
|
+ HASHMAP_FOREACH(pipe, card->pipe_map, j) {
|
|
+ tile = pipe->tile;
|
|
+ if (tile->display)
|
|
+ continue;
|
|
+
|
|
+ assert(!tile->parent);
|
|
+
|
|
+ display = grdev_find_display(session, pipe->name);
|
|
+ if (display && display->tile) {
|
|
+ log_debug("grdev: %s/%s: occupied display for pipe %s",
|
|
+ session->name, card->name, pipe->name);
|
|
+ continue;
|
|
+ } else if (!display) {
|
|
+ r = grdev_display_new(&display, session, pipe->name);
|
|
+ if (r < 0) {
|
|
+ log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
|
|
+ session->name, card->name, pipe->name, strerror(-r));
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ tile_link(pipe->tile, display->tile);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* update displays */
|
|
+ HASHMAP_FOREACH(display, session->display_map, i)
|
|
+ session_change_display(session, display);
|
|
+}
|
|
+
|
|
+grdev_session *grdev_session_pin(grdev_session *session) {
|
|
+ assert(session);
|
|
+
|
|
+ ++session->n_pins;
|
|
+ return session;
|
|
+}
|
|
+
|
|
+grdev_session *grdev_session_unpin(grdev_session *session) {
|
|
+ if (!session)
|
|
+ return NULL;
|
|
+
|
|
+ assert(session->n_pins > 0);
|
|
+
|
|
+ if (--session->n_pins == 0)
|
|
+ session_configure(session);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Contexts
|
|
+ */
|
|
+
|
|
+int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
|
|
+ _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
|
|
+
|
|
+ assert_return(out, -EINVAL);
|
|
+ assert_return(event, -EINVAL);
|
|
+
|
|
+ context = new0(grdev_context, 1);
|
|
+ if (!context)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ context->ref = 1;
|
|
+ context->event = sd_event_ref(event);
|
|
+
|
|
+ if (sysbus)
|
|
+ context->sysbus = sd_bus_ref(sysbus);
|
|
+
|
|
+ context->session_map = hashmap_new(&string_hash_ops);
|
|
+ if (!context->session_map)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ *out = context;
|
|
+ context = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void context_cleanup(grdev_context *context) {
|
|
+ assert(hashmap_size(context->session_map) == 0);
|
|
+
|
|
+ hashmap_free(context->session_map);
|
|
+ context->sysbus = sd_bus_unref(context->sysbus);
|
|
+ context->event = sd_event_unref(context->event);
|
|
+ free(context);
|
|
+}
|
|
+
|
|
+grdev_context *grdev_context_ref(grdev_context *context) {
|
|
+ assert_return(context, NULL);
|
|
+ assert_return(context->ref > 0, NULL);
|
|
+
|
|
+ ++context->ref;
|
|
+ return context;
|
|
+}
|
|
+
|
|
+grdev_context *grdev_context_unref(grdev_context *context) {
|
|
+ if (!context)
|
|
+ return NULL;
|
|
+
|
|
+ assert_return(context->ref > 0, NULL);
|
|
+
|
|
+ if (--context->ref == 0)
|
|
+ context_cleanup(context);
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
diff --git a/src/libsystemd-terminal/grdev.h b/src/libsystemd-terminal/grdev.h
|
|
new file mode 100644
|
|
index 0000000000..2645b12113
|
|
--- /dev/null
|
|
+++ b/src/libsystemd-terminal/grdev.h
|
|
@@ -0,0 +1,182 @@
|
|
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
+
|
|
+/***
|
|
+ This file is part of systemd.
|
|
+
|
|
+ Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
|
|
+
|
|
+ systemd is free software; you can redistribute it and/or modify it
|
|
+ under the terms of the GNU Lesser General Public License as published by
|
|
+ the Free Software Foundation; either version 2.1 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ systemd 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
|
|
+ Lesser General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU Lesser General Public License
|
|
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
+***/
|
|
+
|
|
+/*
|
|
+ * Graphics Devices
|
|
+ * The grdev layer provides generic access to graphics devices. The device
|
|
+ * types are hidden in the implementation and exported in a generic way. The
|
|
+ * grdev_session object forms the base layer. It loads, configures and prepares
|
|
+ * any graphics devices associated with that session. Each session is totally
|
|
+ * independent of other sessions and can be controlled separately.
|
|
+ * The target devices on a session are called display. A display always
|
|
+ * corresponds to a real display regardless how many pipes are needed to drive
|
|
+ * that display. That is, an exported display might internally be created out
|
|
+ * of arbitrary combinations of target pipes. However, this is meant as
|
|
+ * implementation detail and API users must never assume details below the
|
|
+ * display-level. That is, a display is the most low-level object exported.
|
|
+ * Therefore, pipe-configuration and any low-level modesetting is hidden from
|
|
+ * the public API. It is provided by the implementation, and it is the
|
|
+ * implementation that decides how pipes are driven.
|
|
+ *
|
|
+ * The API users are free to ignore specific displays or combine them to create
|
|
+ * larger screens. This often requires user-configuration so is dictated by
|
|
+ * policy. The underlying pipe-configuration might be affected by these
|
|
+ * high-level policies, but is never directly controlled by those. That means,
|
|
+ * depending on the displays you use, it might affect how underlying resources
|
|
+ * are assigned. However, users can never directly apply policies to the pipes,
|
|
+ * but only to displays. In case specific hardware needs quirks on the pipe
|
|
+ * level, we support that via hwdb, not via public user configuration.
|
|
+ *
|
|
+ * Right now, displays are limited to rgb32 memory-mapped framebuffers on the
|
|
+ * primary plane. However, the grdev implementation can be easily extended to
|
|
+ * allow more powerful access (including hardware-acceleration for 2D and 3D
|
|
+ * compositing). So far, this wasn't needed so it is not exposed.
|
|
+ */
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#include <drm_fourcc.h>
|
|
+#include <inttypes.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdlib.h>
|
|
+#include <systemd/sd-bus.h>
|
|
+#include <systemd/sd-event.h>
|
|
+#include "util.h"
|
|
+
|
|
+typedef struct grdev_fb grdev_fb;
|
|
+typedef struct grdev_display_target grdev_display_target;
|
|
+typedef struct grdev_display grdev_display;
|
|
+
|
|
+typedef struct grdev_event grdev_event;
|
|
+typedef struct grdev_session grdev_session;
|
|
+typedef struct grdev_context grdev_context;
|
|
+
|
|
+enum {
|
|
+ /* clockwise rotation; we treat this is abelian group Z4 with ADD */
|
|
+ GRDEV_ROTATE_0 = 0,
|
|
+ GRDEV_ROTATE_90 = 1,
|
|
+ GRDEV_ROTATE_180 = 2,
|
|
+ GRDEV_ROTATE_270 = 3,
|
|
+};
|
|
+
|
|
+enum {
|
|
+ /* flip states; we treat this as abelian group V4 with XOR */
|
|
+ GRDEV_FLIP_NONE = 0x0,
|
|
+ GRDEV_FLIP_HORIZONTAL = 0x1,
|
|
+ GRDEV_FLIP_VERTICAL = 0x2,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Displays
|
|
+ */
|
|
+
|
|
+struct grdev_fb {
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+ uint32_t format;
|
|
+ uint64_t age;
|
|
+ int32_t strides[4];
|
|
+ void *maps[4];
|
|
+};
|
|
+
|
|
+struct grdev_display_target {
|
|
+ uint32_t x;
|
|
+ uint32_t y;
|
|
+ uint32_t width;
|
|
+ uint32_t height;
|
|
+ unsigned int rotate;
|
|
+ unsigned int flip;
|
|
+ const grdev_fb *fb;
|
|
+};
|
|
+
|
|
+bool grdev_display_is_enabled(grdev_display *display);
|
|
+void grdev_display_enable(grdev_display *display);
|
|
+void grdev_display_disable(grdev_display *display);
|
|
+
|
|
+const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage);
|
|
+void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age);
|
|
+
|
|
+#define GRDEV_DISPLAY_FOREACH_TARGET(_display, _t, _minage) \
|
|
+ for ((_t) = grdev_display_next_target((_display), NULL, (_minage)); \
|
|
+ (_t); \
|
|
+ (_t) = grdev_display_next_target((_display), (_t), (_minage)))
|
|
+
|
|
+/*
|
|
+ * Events
|
|
+ */
|
|
+
|
|
+enum {
|
|
+ GRDEV_EVENT_DISPLAY_ADD,
|
|
+ GRDEV_EVENT_DISPLAY_REMOVE,
|
|
+ GRDEV_EVENT_DISPLAY_CHANGE,
|
|
+ GRDEV_EVENT_DISPLAY_FRAME,
|
|
+};
|
|
+
|
|
+typedef void (*grdev_event_fn) (grdev_session *session, void *userdata, grdev_event *ev);
|
|
+
|
|
+struct grdev_event {
|
|
+ unsigned int type;
|
|
+ union {
|
|
+ struct {
|
|
+ grdev_display *display;
|
|
+ } display_add, display_remove, display_change;
|
|
+
|
|
+ struct {
|
|
+ grdev_display *display;
|
|
+ } display_frame;
|
|
+ };
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Sessions
|
|
+ */
|
|
+
|
|
+enum {
|
|
+ GRDEV_SESSION_CUSTOM = (1 << 0),
|
|
+ GRDEV_SESSION_MANAGED = (1 << 1),
|
|
+};
|
|
+
|
|
+int grdev_session_new(grdev_session **out,
|
|
+ grdev_context *context,
|
|
+ unsigned int flags,
|
|
+ const char *name,
|
|
+ grdev_event_fn event_fn,
|
|
+ void *userdata);
|
|
+grdev_session *grdev_session_free(grdev_session *session);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_free);
|
|
+
|
|
+bool grdev_session_is_enabled(grdev_session *session);
|
|
+void grdev_session_enable(grdev_session *session);
|
|
+void grdev_session_disable(grdev_session *session);
|
|
+
|
|
+void grdev_session_commit(grdev_session *session);
|
|
+void grdev_session_restore(grdev_session *session);
|
|
+
|
|
+/*
|
|
+ * Contexts
|
|
+ */
|
|
+
|
|
+int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus);
|
|
+grdev_context *grdev_context_ref(grdev_context *context);
|
|
+grdev_context *grdev_context_unref(grdev_context *context);
|
|
+
|
|
+DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_context*, grdev_context_unref);
|