diff options
Diffstat (limited to 'src/libsystemd-terminal/grdev.c')
-rw-r--r-- | src/libsystemd-terminal/grdev.c | 1219 |
1 files changed, 1219 insertions, 0 deletions
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; +} |