/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. Copyright (C) 2014 David Herrmann 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 . ***/ #include #include #include #include #include #include #include #include "grdev.h" #include "grdev-internal.h" #include "hashmap.h" #include "login-shared.h" #include "macro.h" #include "udev-util.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->children_by_node_next) ? : _i->parent) #define TILE_FOREACH_SAFE(_root, _i, _next) \ for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->children_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_children; LIST_PREPEND(children_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_children > 0); --parent->node.n_children; LIST_REMOVE(children_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 children 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_children < 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(out, -EINVAL); 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_children == 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; } void grdev_display_set_userdata(grdev_display *display, void *userdata) { assert(display); display->userdata = userdata; } void *grdev_display_get_userdata(grdev_display *display) { assert_return(display, NULL); return display->userdata; } const char *grdev_display_get_name(grdev_display *display) { assert_return(display, NULL); return display->name; } uint32_t grdev_display_get_width(grdev_display *display) { assert_return(display, 0); return display->width; } uint32_t grdev_display_get_height(grdev_display *display) { assert_return(display, 0); return display->height; } 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 + 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; assert(fb == pipe->back); } /* 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 children before parent */ for (tile = tile_leftmost(display->tile); tile; tile = tile_leftmost(tile->children_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 its children 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(children_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); display->width = display->tile->cache_w; display->height = display->tile->cache_h; 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); } static int pipe_vsync_fn(sd_event_source *src, uint64_t usec, void *userdata) { grdev_pipe *pipe = userdata; grdev_pipe_frame(pipe); return 0; } 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->vrefresh > 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 = sd_event_add_time(pipe->card->session->context->event, &pipe->vsync_src, CLOCK_MONOTONIC, 0, 10 * USEC_PER_MSEC, pipe_vsync_fn, pipe); if (r < 0) return r; r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); 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); sd_event_source_unref(tmp.vsync_src); 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 && pipe->enabled) { grdev_display *display = pipe->tile->display; assert(display); if (running) 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 || !pipe->enabled) return; display = pipe->tile->display; assert(display); grdev_pipe_schedule(pipe, 0); session_frame(display->session, display); } void grdev_pipe_schedule(grdev_pipe *pipe, uint64_t frames) { int r; uint64_t ts; if (!frames) { sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); return; } r = sd_event_now(pipe->card->session->context->event, CLOCK_MONOTONIC, &ts); if (r < 0) goto error; ts += frames * USEC_PER_MSEC * 1000ULL / pipe->vrefresh; r = sd_event_source_set_time(pipe->vsync_src, ts); if (r < 0) goto error; r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_ONESHOT); if (r < 0) goto error; return; error: log_debug("grdev: %s/%s/%s: cannot schedule vsync timer: %s", pipe->card->session->name, pipe->card->name, pipe->name, strerror(-r)); } /* * 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); session_frame(session, display); } else if (changed) { session_raise_display_change(session, display); session_frame(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); } void grdev_session_add_drm(grdev_session *session, struct udev_device *ud) { grdev_card *card; dev_t devnum; int r; assert(session); assert(ud); devnum = udev_device_get_devnum(ud); if (devnum == 0) return grdev_session_hotplug_drm(session, ud); card = grdev_find_drm_card(session, devnum); if (card) return; r = grdev_drm_card_new(&card, session, ud); if (r < 0) { log_debug("grdev: %s: cannot add DRM device for %s: %s", session->name, udev_device_get_syspath(ud), strerror(-r)); return; } session_add_card(session, card); } void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud) { grdev_card *card; dev_t devnum; assert(session); assert(ud); devnum = udev_device_get_devnum(ud); if (devnum == 0) return grdev_session_hotplug_drm(session, ud); card = grdev_find_drm_card(session, devnum); if (!card) return; session_remove_card(session, card); } void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud) { grdev_card *card = NULL; struct udev_device *p; dev_t devnum; assert(session); assert(ud); for (p = ud; p; p = udev_device_get_parent_with_subsystem_devtype(p, "drm", NULL)) { devnum = udev_device_get_devnum(ud); if (devnum == 0) continue; card = grdev_find_drm_card(session, devnum); if (card) break; } if (!card) return; grdev_drm_card_hotplug(card, ud); } 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; }