chiark / gitweb /
terminal: grdev: raise frame event after DISPLAY_ADD/CHANGE
[elogind.git] / src / libsystemd-terminal / grdev.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <inttypes.h>
23 #include <libudev.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <systemd/sd-bus.h>
27 #include <systemd/sd-event.h>
28 #include <systemd/sd-login.h>
29 #include "grdev.h"
30 #include "grdev-internal.h"
31 #include "hashmap.h"
32 #include "login-shared.h"
33 #include "macro.h"
34 #include "udev-util.h"
35 #include "util.h"
36
37 static void pipe_enable(grdev_pipe *pipe);
38 static void pipe_disable(grdev_pipe *pipe);
39 static void card_modified(grdev_card *card);
40 static void session_frame(grdev_session *session, grdev_display *display);
41
42 /*
43  * Displays
44  */
45
46 static inline grdev_tile *tile_leftmost(grdev_tile *tile) {
47         if (!tile)
48                 return NULL;
49
50         while (tile->type == GRDEV_TILE_NODE && tile->node.child_list)
51                 tile = tile->node.child_list;
52
53         return tile;
54 }
55
56 #define TILE_FOREACH(_root, _i) \
57         for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->childs_by_node_next) ? : _i->parent)
58
59 #define TILE_FOREACH_SAFE(_root, _i, _next) \
60         for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->childs_by_node_next) ? : _i->parent), true); _i = _next)
61
62 static void tile_link(grdev_tile *tile, grdev_tile *parent) {
63         grdev_display *display;
64         grdev_tile *t;
65
66         assert(tile);
67         assert(!tile->parent);
68         assert(!tile->display);
69         assert(parent);
70         assert(parent->type == GRDEV_TILE_NODE);
71
72         display = parent->display;
73
74         assert(!display || !display->enabled);
75
76         ++parent->node.n_childs;
77         LIST_PREPEND(childs_by_node, parent->node.child_list, tile);
78         tile->parent = parent;
79
80         if (display) {
81                 display->modified = true;
82                 TILE_FOREACH(tile, t) {
83                         t->display = display;
84                         if (t->type == GRDEV_TILE_LEAF) {
85                                 ++display->n_leafs;
86                                 if (display->enabled)
87                                         pipe_enable(t->leaf.pipe);
88                         }
89                 }
90         }
91 }
92
93 static void tile_unlink(grdev_tile *tile) {
94         grdev_tile *parent, *t;
95         grdev_display *display;
96
97         assert(tile);
98
99         display = tile->display;
100         parent = tile->parent;
101         if (!parent) {
102                 assert(!display);
103                 return;
104         }
105
106         assert(parent->type == GRDEV_TILE_NODE);
107         assert(parent->display == display);
108         assert(parent->node.n_childs > 0);
109
110         --parent->node.n_childs;
111         LIST_REMOVE(childs_by_node, parent->node.child_list, tile);
112         tile->parent = NULL;
113
114         if (display) {
115                 display->modified = true;
116                 TILE_FOREACH(tile, t) {
117                         t->display = NULL;
118                         if (t->type == GRDEV_TILE_LEAF) {
119                                 --display->n_leafs;
120                                 t->leaf.pipe->cache = NULL;
121                                 pipe_disable(t->leaf.pipe);
122                         }
123                 }
124         }
125
126         /* Tile trees are driven by leafs. Internal nodes have no owner, thus,
127          * we must take care to not leave them around. Therefore, whenever we
128          * unlink any part of a tree, we also destroy the parent, in case it's
129          * now stale.
130          * Parents are stale if they have no childs and either have no display
131          * or if they are intermediate nodes (i.e, they have a parent).
132          * This means, you can easily create trees, but you can never partially
133          * move or destruct them so far. They're always reduced to minimal form
134          * if you cut them. This might change later, but so far we didn't need
135          * partial destruction or the ability to move whole trees. */
136
137         if (parent->node.n_childs < 1 && (parent->parent || !parent->display))
138                 grdev_tile_free(parent);
139 }
140
141 static int tile_new(grdev_tile **out) {
142         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
143
144         assert(out);
145
146         tile = new0(grdev_tile, 1);
147         if (!tile)
148                 return -ENOMEM;
149
150         tile->type = (unsigned)-1;
151
152         *out = tile;
153         tile = NULL;
154         return 0;
155 }
156
157 int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) {
158         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
159         int r;
160
161         assert_return(pipe, -EINVAL);
162         assert_return(!pipe->tile, -EINVAL);
163
164         r = tile_new(&tile);
165         if (r < 0)
166                 return r;
167
168         tile->type = GRDEV_TILE_LEAF;
169         tile->leaf.pipe = pipe;
170
171         if (out)
172                 *out = tile;
173         tile = NULL;
174         return 0;
175 }
176
177 int grdev_tile_new_node(grdev_tile **out) {
178         _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
179         int r;
180
181         assert_return(out, -EINVAL);
182
183         r = tile_new(&tile);
184         if (r < 0)
185                 return r;
186
187         tile->type = GRDEV_TILE_NODE;
188
189         *out = tile;
190         tile = NULL;
191         return 0;
192 }
193
194 grdev_tile *grdev_tile_free(grdev_tile *tile) {
195         if (!tile)
196                 return NULL;
197
198         tile_unlink(tile);
199
200         switch (tile->type) {
201         case GRDEV_TILE_LEAF:
202                 assert(!tile->parent);
203                 assert(!tile->display);
204                 assert(tile->leaf.pipe);
205
206                 break;
207         case GRDEV_TILE_NODE:
208                 assert(!tile->parent);
209                 assert(!tile->display);
210                 assert(tile->node.n_childs == 0);
211
212                 break;
213         }
214
215         free(tile);
216
217         return NULL;
218 }
219
220 grdev_display *grdev_find_display(grdev_session *session, const char *name) {
221         assert_return(session, NULL);
222         assert_return(name, NULL);
223
224         return hashmap_get(session->display_map, name);
225 }
226
227 int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) {
228         _cleanup_(grdev_display_freep) grdev_display *display = NULL;
229         int r;
230
231         assert(session);
232         assert(name);
233
234         display = new0(grdev_display, 1);
235         if (!display)
236                 return -ENOMEM;
237
238         display->session = session;
239
240         display->name = strdup(name);
241         if (!display->name)
242                 return -ENOMEM;
243
244         r = grdev_tile_new_node(&display->tile);
245         if (r < 0)
246                 return r;
247
248         display->tile->display = display;
249
250         r = hashmap_put(session->display_map, display->name, display);
251         if (r < 0)
252                 return r;
253
254         if (out)
255                 *out = display;
256         display = NULL;
257         return 0;
258 }
259
260 grdev_display *grdev_display_free(grdev_display *display) {
261         if (!display)
262                 return NULL;
263
264         assert(!display->public);
265         assert(!display->enabled);
266         assert(!display->modified);
267         assert(display->n_leafs == 0);
268         assert(display->n_pipes == 0);
269
270         if (display->name)
271                 hashmap_remove_value(display->session->display_map, display->name, display);
272
273         if (display->tile) {
274                 display->tile->display = NULL;
275                 grdev_tile_free(display->tile);
276         }
277
278         free(display->pipes);
279         free(display->name);
280         free(display);
281
282         return NULL;
283 }
284
285 bool grdev_display_is_enabled(grdev_display *display) {
286         return display && display->enabled;
287 }
288
289 void grdev_display_enable(grdev_display *display) {
290         grdev_tile *t;
291
292         assert(display);
293
294         if (!display->enabled) {
295                 display->enabled = true;
296                 TILE_FOREACH(display->tile, t)
297                         if (t->type == GRDEV_TILE_LEAF)
298                                 pipe_enable(t->leaf.pipe);
299         }
300 }
301
302 void grdev_display_disable(grdev_display *display) {
303         grdev_tile *t;
304
305         assert(display);
306
307         if (display->enabled) {
308                 display->enabled = false;
309                 TILE_FOREACH(display->tile, t)
310                         if (t->type == GRDEV_TILE_LEAF)
311                                 pipe_disable(t->leaf.pipe);
312         }
313 }
314
315 const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage) {
316         grdev_display_cache *cache;
317         size_t idx;
318
319         assert_return(display, NULL);
320         assert_return(!display->modified, NULL);
321         assert_return(display->enabled, NULL);
322
323         if (prev) {
324                 cache = container_of(prev, grdev_display_cache, target);
325
326                 assert(cache->pipe);
327                 assert(cache->pipe->tile->display == display);
328                 assert(display->pipes >= cache);
329
330                 idx = (cache - display->pipes) / sizeof(*cache) + 1;
331         } else {
332                 idx = 0;
333         }
334
335         for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) {
336                 grdev_display_target *target;
337                 grdev_pipe *pipe;
338                 grdev_fb *fb;
339
340                 pipe = cache->pipe;
341                 target = &cache->target;
342
343                 if (!pipe->running || !pipe->enabled)
344                         continue;
345
346                 /* if front-buffer is up-to-date, there's nothing to do */
347                 if (minage > 0 && pipe->front && pipe->front->age >= minage)
348                         continue;
349
350                 /* find suitable back-buffer */
351                 if (!(fb = pipe->back)) {
352                         if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
353                                 continue;
354                 }
355
356                 /* if back-buffer is up-to-date, schedule flip */
357                 if (minage > 0 && fb->age >= minage) {
358                         grdev_display_flip_target(display, target, fb->age);
359                         continue;
360                 }
361
362                 /* we have an out-of-date back-buffer; return for redraw */
363                 target->fb = fb;
364                 return target;
365         }
366
367         return NULL;
368 }
369
370 void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age) {
371         grdev_display_cache *cache;
372         size_t i;
373
374         assert(display);
375         assert(!display->modified);
376         assert(display->enabled);
377         assert(target);
378         assert(target->fb);
379
380         cache = container_of(target, grdev_display_cache, target);
381
382         assert(cache->pipe);
383         assert(cache->pipe->tile->display == display);
384
385         /* reset age of all FB on overflow */
386         if (age < target->fb->age)
387                 for (i = 0; i < cache->pipe->max_fbs; ++i)
388                         if (cache->pipe->fbs[i])
389                                 cache->pipe->fbs[i]->age = 0;
390
391         ((grdev_fb*)target->fb)->age = age;
392         cache->pipe->flip = true;
393 }
394
395 static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) {
396         uint32_t x, y, width, height;
397         grdev_display_target *t;
398
399         assert(c);
400         assert(l);
401         assert(l->cache_w >= c->target.width + c->target.x);
402         assert(l->cache_h >= c->target.height + c->target.y);
403
404         t = &c->target;
405
406         /* rotate child */
407
408         t->rotate = (t->rotate + l->rotate) & 0x3;
409
410         x = t->x;
411         y = t->y;
412         width = t->width;
413         height = t->height;
414
415         switch (l->rotate) {
416         case GRDEV_ROTATE_0:
417                 break;
418         case GRDEV_ROTATE_90:
419                 t->x = l->cache_h - (height + y);
420                 t->y = x;
421                 t->width = height;
422                 t->height = width;
423                 break;
424         case GRDEV_ROTATE_180:
425                 t->x = l->cache_w - (width + x);
426                 t->y = l->cache_h - (height + y);
427                 break;
428         case GRDEV_ROTATE_270:
429                 t->x = y;
430                 t->y = l->cache_w - (width + x);
431                 t->width = height;
432                 t->height = width;
433                 break;
434         }
435
436         /* flip child */
437
438         t->flip ^= l->flip;
439
440         if (l->flip & GRDEV_FLIP_HORIZONTAL)
441                 t->x = l->cache_w - (t->width + t->x);
442         if (l->flip & GRDEV_FLIP_VERTICAL)
443                 t->y = l->cache_h - (t->height + t->y);
444
445         /* move child */
446
447         t->x += l->x;
448         t->y += l->y;
449 }
450
451 static void display_cache_targets(grdev_display *display) {
452         grdev_display_cache *c;
453         grdev_tile *tile;
454
455         assert(display);
456
457         /* depth-first with childs before parent */
458         for (tile = tile_leftmost(display->tile);
459              tile;
460              tile = tile_leftmost(tile->childs_by_node_next) ? : tile->parent) {
461                 if (tile->type == GRDEV_TILE_LEAF) {
462                         grdev_pipe *p;
463
464                         /* We're at a leaf and no parent has been cached, yet.
465                          * Copy the pipe information into the target cache and
466                          * update our global pipe-caches if required. */
467
468                         assert(tile->leaf.pipe);
469                         assert(display->n_pipes + 1 <= display->max_pipes);
470
471                         p = tile->leaf.pipe;
472                         c = &display->pipes[display->n_pipes++];
473
474                         zero(*c);
475                         c->pipe = p;
476                         c->pipe->cache = c;
477                         c->target.width = p->width;
478                         c->target.height = p->height;
479                         tile->cache_w = p->width;
480                         tile->cache_h = p->height;
481
482                         /* all new tiles are incomplete due to geometry changes */
483                         c->incomplete = true;
484
485                         display_cache_apply(c, tile);
486                 } else {
487                         grdev_tile *child, *l;
488
489                         /* We're now at a node with all it's childs already
490                          * computed (depth-first, child before parent). We
491                          * first need to know the size of our tile, then we
492                          * recurse into all leafs and update their cache. */
493
494                         tile->cache_w = 0;
495                         tile->cache_h = 0;
496
497                         LIST_FOREACH(childs_by_node, child, tile->node.child_list) {
498                                 if (child->x + child->cache_w > tile->cache_w)
499                                         tile->cache_w = child->x + child->cache_w;
500                                 if (child->y + child->cache_h > tile->cache_h)
501                                         tile->cache_h = child->y + child->cache_h;
502                         }
503
504                         assert(tile->cache_w > 0);
505                         assert(tile->cache_h > 0);
506
507                         TILE_FOREACH(tile, l)
508                                 if (l->type == GRDEV_TILE_LEAF)
509                                         display_cache_apply(l->leaf.pipe->cache, tile);
510                 }
511         }
512 }
513
514 static bool display_cache(grdev_display *display) {
515         grdev_tile *tile;
516         size_t n;
517         void *t;
518         int r;
519
520         assert(display);
521
522         if (!display->modified)
523                 return false;
524
525         display->modified = false;
526         display->framed = false;
527         display->n_pipes = 0;
528         display->width = 0;
529         display->height = 0;
530
531         if (display->n_leafs < 1)
532                 return false;
533
534         TILE_FOREACH(display->tile, tile)
535                 if (tile->type == GRDEV_TILE_LEAF)
536                         tile->leaf.pipe->cache = NULL;
537
538         if (display->n_leafs > display->max_pipes) {
539                 n = ALIGN_POWER2(display->n_leafs);
540                 if (!n) {
541                         r = -ENOMEM;
542                         goto out;
543                 }
544
545                 t = realloc_multiply(display->pipes, sizeof(*display->pipes), n);
546                 if (!t) {
547                         r = -ENOMEM;
548                         goto out;
549                 }
550
551                 display->pipes = t;
552                 display->max_pipes = n;
553         }
554
555         display_cache_targets(display);
556
557         r = 0;
558
559 out:
560         if (r < 0)
561                 log_debug("grdev: %s/%s: cannot cache pipes: %s",
562                           display->session->name, display->name, strerror(-r));
563         return true;
564 }
565
566 /*
567  * Pipes
568  */
569
570 grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) {
571         assert_return(card, NULL);
572         assert_return(name, NULL);
573
574         return hashmap_get(card->pipe_map, name);
575 }
576
577 int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) {
578         int r;
579
580         assert_return(pipe, -EINVAL);
581         assert_return(pipe->vtable, -EINVAL);
582         assert_return(pipe->vtable->free, -EINVAL);
583         assert_return(pipe->card, -EINVAL);
584         assert_return(pipe->card->session, -EINVAL);
585         assert_return(!pipe->cache, -EINVAL);
586         assert_return(pipe->width > 0, -EINVAL);
587         assert_return(pipe->height > 0, -EINVAL);
588         assert_return(!pipe->enabled, -EINVAL);
589         assert_return(!pipe->running, -EINVAL);
590         assert_return(name, -EINVAL);
591
592         pipe->name = strdup(name);
593         if (!pipe->name)
594                 return -ENOMEM;
595
596         if (n_fbs > 0) {
597                 pipe->fbs = new0(grdev_fb*, n_fbs);
598                 if (!pipe->fbs)
599                         return -ENOMEM;
600
601                 pipe->max_fbs = n_fbs;
602         }
603
604         r = grdev_tile_new_leaf(&pipe->tile, pipe);
605         if (r < 0)
606                 return r;
607
608         r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe);
609         if (r < 0)
610                 return r;
611
612         card_modified(pipe->card);
613         return 0;
614 }
615
616 grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) {
617         grdev_pipe tmp;
618
619         if (!pipe)
620                 return NULL;
621
622         assert(pipe->card);
623         assert(pipe->vtable);
624         assert(pipe->vtable->free);
625
626         if (pipe->name)
627                 hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe);
628         if (pipe->tile)
629                 tile_unlink(pipe->tile);
630
631         assert(!pipe->cache);
632
633         tmp = *pipe;
634         pipe->vtable->free(pipe);
635
636         grdev_tile_free(tmp.tile);
637         card_modified(tmp.card);
638         free(tmp.fbs);
639         free(tmp.name);
640
641         return NULL;
642 }
643
644 static void pipe_enable(grdev_pipe *pipe) {
645         assert(pipe);
646
647         if (!pipe->enabled) {
648                 pipe->enabled = true;
649                 if (pipe->vtable->enable)
650                         pipe->vtable->enable(pipe);
651         }
652 }
653
654 static void pipe_disable(grdev_pipe *pipe) {
655         assert(pipe);
656
657         if (pipe->enabled) {
658                 pipe->enabled = false;
659                 if (pipe->vtable->disable)
660                         pipe->vtable->disable(pipe);
661         }
662 }
663
664 void grdev_pipe_ready(grdev_pipe *pipe, bool running) {
665         assert(pipe);
666
667         /* grdev_pipe_ready() is used by backends to notify about pipe state
668          * changed. If a pipe is ready, it can be fully used by us (available,
669          * enabled and accessable). Backends can disable pipes at any time
670          * (like for async revocation), but can only enable them from parent
671          * context. Otherwise, we might call user-callbacks recursively. */
672
673         if (pipe->running == running)
674                 return;
675
676         pipe->running = running;
677
678         /* runtime events for unused pipes are not interesting */
679         if (pipe->cache) {
680                 grdev_display *display = pipe->tile->display;
681
682                 assert(display);
683
684                 if (running) {
685                         if (pipe->enabled)
686                                 session_frame(display->session, display);
687                 } else {
688                         pipe->cache->incomplete = true;
689                 }
690         }
691 }
692
693 void grdev_pipe_frame(grdev_pipe *pipe) {
694         grdev_display *display;
695
696         assert(pipe);
697
698         /* if pipe is unused, ignore any frame events */
699         if (!pipe->cache)
700                 return;
701
702         display = pipe->tile->display;
703         assert(display);
704
705         if (pipe->enabled)
706                 session_frame(display->session, display);
707 }
708
709 /*
710  * Cards
711  */
712
713 grdev_card *grdev_find_card(grdev_session *session, const char *name) {
714         assert_return(session, NULL);
715         assert_return(name, NULL);
716
717         return hashmap_get(session->card_map, name);
718 }
719
720 int grdev_card_add(grdev_card *card, const char *name) {
721         int r;
722
723         assert_return(card, -EINVAL);
724         assert_return(card->vtable, -EINVAL);
725         assert_return(card->vtable->free, -EINVAL);
726         assert_return(card->session, -EINVAL);
727         assert_return(name, -EINVAL);
728
729         card->name = strdup(name);
730         if (!card->name)
731                 return -ENOMEM;
732
733         card->pipe_map = hashmap_new(&string_hash_ops);
734         if (!card->pipe_map)
735                 return -ENOMEM;
736
737         r = hashmap_put(card->session->card_map, card->name, card);
738         if (r < 0)
739                 return r;
740
741         return 0;
742 }
743
744 grdev_card *grdev_card_free(grdev_card *card) {
745         grdev_card tmp;
746
747         if (!card)
748                 return NULL;
749
750         assert(!card->enabled);
751         assert(card->vtable);
752         assert(card->vtable->free);
753
754         if (card->name)
755                 hashmap_remove_value(card->session->card_map, card->name, card);
756
757         tmp = *card;
758         card->vtable->free(card);
759
760         assert(hashmap_size(tmp.pipe_map) == 0);
761
762         hashmap_free(tmp.pipe_map);
763         free(tmp.name);
764
765         return NULL;
766 }
767
768 static void card_modified(grdev_card *card) {
769         assert(card);
770         assert(card->session->n_pins > 0);
771
772         card->modified = true;
773 }
774
775 static void grdev_card_enable(grdev_card *card) {
776         assert(card);
777
778         if (!card->enabled) {
779                 card->enabled = true;
780                 if (card->vtable->enable)
781                         card->vtable->enable(card);
782         }
783 }
784
785 static void grdev_card_disable(grdev_card *card) {
786         assert(card);
787
788         if (card->enabled) {
789                 card->enabled = false;
790                 if (card->vtable->disable)
791                         card->vtable->disable(card);
792         }
793 }
794
795 /*
796  * Sessions
797  */
798
799 static void session_raise(grdev_session *session, grdev_event *event) {
800         session->event_fn(session, session->userdata, event);
801 }
802
803 static void session_raise_display_add(grdev_session *session, grdev_display *display) {
804         grdev_event event = {
805                 .type = GRDEV_EVENT_DISPLAY_ADD,
806                 .display_add = {
807                         .display = display,
808                 },
809         };
810
811         session_raise(session, &event);
812 }
813
814 static void session_raise_display_remove(grdev_session *session, grdev_display *display) {
815         grdev_event event = {
816                 .type = GRDEV_EVENT_DISPLAY_REMOVE,
817                 .display_remove = {
818                         .display = display,
819                 },
820         };
821
822         session_raise(session, &event);
823 }
824
825 static void session_raise_display_change(grdev_session *session, grdev_display *display) {
826         grdev_event event = {
827                 .type = GRDEV_EVENT_DISPLAY_CHANGE,
828                 .display_change = {
829                         .display = display,
830                 },
831         };
832
833         session_raise(session, &event);
834 }
835
836 static void session_raise_display_frame(grdev_session *session, grdev_display *display) {
837         grdev_event event = {
838                 .type = GRDEV_EVENT_DISPLAY_FRAME,
839                 .display_frame = {
840                         .display = display,
841                 },
842         };
843
844         session_raise(session, &event);
845 }
846
847 static void session_add_card(grdev_session *session, grdev_card *card) {
848         assert(session);
849         assert(card);
850
851         log_debug("grdev: %s: add card '%s'", session->name, card->name);
852
853         /* Cards are not exposed to users, but managed internally. Cards are
854          * enabled if the session is enabled, and will track that state. The
855          * backend can probe the card at any time, but only if enabled. It
856          * will then add pipes according to hardware state.
857          * That is, the card may create pipes as soon as we enable it here. */
858
859         if (session->enabled)
860                 grdev_card_enable(card);
861 }
862
863 static void session_remove_card(grdev_session *session, grdev_card *card) {
864         assert(session);
865         assert(card);
866
867         log_debug("grdev: %s: remove card '%s'", session->name, card->name);
868
869         /* As cards are not exposed, it can never be accessed by outside
870          * users and we can simply remove it. Disabling the card does not
871          * necessarily drop all pipes of the card. This is usually deferred
872          * to card destruction (as pipes are cached as long as FDs remain
873          * open). Therefore, the card destruction might cause pipes, and thus
874          * visible displays, to be removed. */
875
876         grdev_card_disable(card);
877         grdev_card_free(card);
878 }
879
880 static void session_add_display(grdev_session *session, grdev_display *display) {
881         assert(session);
882         assert(display);
883         assert(!display->enabled);
884
885         log_debug("grdev: %s: add display '%s'", session->name, display->name);
886
887         /* Displays are the main entity for public API users. We create them
888          * independent of card backends and they wrap any underlying display
889          * architecture. Displays are public at all times, thus, may be entered
890          * by outside users at any time. */
891
892         display->public = true;
893         session_raise_display_add(session, display);
894 }
895
896 static void session_remove_display(grdev_session *session, grdev_display *display) {
897         assert(session);
898         assert(display);
899
900         log_debug("grdev: %s: remove display '%s'", session->name, display->name);
901
902         /* Displays are public, so we have to be careful when removing them.
903          * We first tell users about their removal, disable them and then drop
904          * them. We now, after the notification, no external access will
905          * happen. Therefore, we can release the tiles afterwards safely. */
906
907         if (display->public) {
908                 display->public = false;
909                 session_raise_display_remove(session, display);
910         }
911
912         grdev_display_disable(display);
913         grdev_display_free(display);
914 }
915
916 static void session_change_display(grdev_session *session, grdev_display *display) {
917         bool changed;
918
919         assert(session);
920         assert(display);
921
922         changed = display_cache(display);
923
924         if (display->n_leafs == 0) {
925                 session_remove_display(session, display);
926         } else if (!display->public) {
927                 session_add_display(session, display);
928                 session_frame(session, display);
929         } else if (changed) {
930                 session_raise_display_change(session, display);
931                 session_frame(session, display);
932         } else if (display->framed) {
933                 session_frame(session, display);
934         }
935 }
936
937 static void session_frame(grdev_session *session, grdev_display *display) {
938         assert(session);
939         assert(display);
940
941         display->framed = false;
942
943         if (!display->enabled || !session->enabled)
944                 return;
945
946         if (session->n_pins > 0)
947                 display->framed = true;
948         else
949                 session_raise_display_frame(session, display);
950 }
951
952 int grdev_session_new(grdev_session **out,
953                       grdev_context *context,
954                       unsigned int flags,
955                       const char *name,
956                       grdev_event_fn event_fn,
957                       void *userdata) {
958         _cleanup_(grdev_session_freep) grdev_session *session = NULL;
959         int r;
960
961         assert(out);
962         assert(context);
963         assert(name);
964         assert(event_fn);
965         assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL);
966         assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL);
967         assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL);
968
969         session = new0(grdev_session, 1);
970         if (!session)
971                 return -ENOMEM;
972
973         session->context = grdev_context_ref(context);
974         session->custom = flags & GRDEV_SESSION_CUSTOM;
975         session->managed = flags & GRDEV_SESSION_MANAGED;
976         session->event_fn = event_fn;
977         session->userdata = userdata;
978
979         session->name = strdup(name);
980         if (!session->name)
981                 return -ENOMEM;
982
983         if (session->managed) {
984                 r = sd_bus_path_encode("/org/freedesktop/login1/session",
985                                        session->name, &session->path);
986                 if (r < 0)
987                         return r;
988         }
989
990         session->card_map = hashmap_new(&string_hash_ops);
991         if (!session->card_map)
992                 return -ENOMEM;
993
994         session->display_map = hashmap_new(&string_hash_ops);
995         if (!session->display_map)
996                 return -ENOMEM;
997
998         r = hashmap_put(context->session_map, session->name, session);
999         if (r < 0)
1000                 return r;
1001
1002         *out = session;
1003         session = NULL;
1004         return 0;
1005 }
1006
1007 grdev_session *grdev_session_free(grdev_session *session) {
1008         grdev_card *card;
1009
1010         if (!session)
1011                 return NULL;
1012
1013         grdev_session_disable(session);
1014
1015         while ((card = hashmap_first(session->card_map)))
1016                 session_remove_card(session, card);
1017
1018         assert(hashmap_size(session->display_map) == 0);
1019
1020         if (session->name)
1021                 hashmap_remove_value(session->context->session_map, session->name, session);
1022
1023         hashmap_free(session->display_map);
1024         hashmap_free(session->card_map);
1025         session->context = grdev_context_unref(session->context);
1026         free(session->path);
1027         free(session->name);
1028         free(session);
1029
1030         return NULL;
1031 }
1032
1033 bool grdev_session_is_enabled(grdev_session *session) {
1034         return session && session->enabled;
1035 }
1036
1037 void grdev_session_enable(grdev_session *session) {
1038         grdev_card *card;
1039         Iterator iter;
1040
1041         assert(session);
1042
1043         if (!session->enabled) {
1044                 session->enabled = true;
1045                 HASHMAP_FOREACH(card, session->card_map, iter)
1046                         grdev_card_enable(card);
1047         }
1048 }
1049
1050 void grdev_session_disable(grdev_session *session) {
1051         grdev_card *card;
1052         Iterator iter;
1053
1054         assert(session);
1055
1056         if (session->enabled) {
1057                 session->enabled = false;
1058                 HASHMAP_FOREACH(card, session->card_map, iter)
1059                         grdev_card_disable(card);
1060         }
1061 }
1062
1063 void grdev_session_commit(grdev_session *session) {
1064         grdev_card *card;
1065         Iterator iter;
1066
1067         assert(session);
1068
1069         if (!session->enabled)
1070                 return;
1071
1072         HASHMAP_FOREACH(card, session->card_map, iter)
1073                 if (card->vtable->commit)
1074                         card->vtable->commit(card);
1075 }
1076
1077 void grdev_session_restore(grdev_session *session) {
1078         grdev_card *card;
1079         Iterator iter;
1080
1081         assert(session);
1082
1083         if (!session->enabled)
1084                 return;
1085
1086         HASHMAP_FOREACH(card, session->card_map, iter)
1087                 if (card->vtable->restore)
1088                         card->vtable->restore(card);
1089 }
1090
1091 void grdev_session_add_drm(grdev_session *session, struct udev_device *ud) {
1092         grdev_card *card;
1093         dev_t devnum;
1094         int r;
1095
1096         assert(session);
1097         assert(ud);
1098
1099         devnum = udev_device_get_devnum(ud);
1100         if (devnum == 0)
1101                 return grdev_session_hotplug_drm(session, ud);
1102
1103         card = grdev_find_drm_card(session, devnum);
1104         if (card)
1105                 return;
1106
1107         r = grdev_drm_card_new(&card, session, ud);
1108         if (r < 0) {
1109                 log_debug("grdev: %s: cannot add DRM device for %s: %s",
1110                           session->name, udev_device_get_syspath(ud), strerror(-r));
1111                 return;
1112         }
1113
1114         session_add_card(session, card);
1115 }
1116
1117 void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud) {
1118         grdev_card *card;
1119         dev_t devnum;
1120
1121         assert(session);
1122         assert(ud);
1123
1124         devnum = udev_device_get_devnum(ud);
1125         if (devnum == 0)
1126                 return grdev_session_hotplug_drm(session, ud);
1127
1128         card = grdev_find_drm_card(session, devnum);
1129         if (!card)
1130                 return;
1131
1132         session_remove_card(session, card);
1133 }
1134
1135 void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud) {
1136         grdev_card *card = NULL;
1137         struct udev_device *p;
1138         dev_t devnum;
1139
1140         assert(session);
1141         assert(ud);
1142
1143         for (p = ud; p; p = udev_device_get_parent_with_subsystem_devtype(p, "drm", NULL)) {
1144                 devnum = udev_device_get_devnum(ud);
1145                 if (devnum == 0)
1146                         continue;
1147
1148                 card = grdev_find_drm_card(session, devnum);
1149                 if (card)
1150                         break;
1151         }
1152
1153         if (!card)
1154                 return;
1155
1156         grdev_drm_card_hotplug(card, ud);
1157 }
1158
1159 static void session_configure(grdev_session *session) {
1160         grdev_display *display;
1161         grdev_tile *tile;
1162         grdev_card *card;
1163         grdev_pipe *pipe;
1164         Iterator i, j;
1165         int r;
1166
1167         assert(session);
1168
1169         /*
1170          * Whenever backends add or remove pipes, we set session->modified and
1171          * require them to pin the session while modifying it. On release, we
1172          * reconfigure the device and re-assign displays to all modified pipes.
1173          *
1174          * So far, we configure each pipe as a separate display. We do not
1175          * support user-configuration, nor have we gotten any reports from
1176          * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
1177          * we get reports, we keep the logic to a minimum.
1178          */
1179
1180         /* create new displays for all unconfigured pipes */
1181         HASHMAP_FOREACH(card, session->card_map, i) {
1182                 if (!card->modified)
1183                         continue;
1184
1185                 card->modified = false;
1186
1187                 HASHMAP_FOREACH(pipe, card->pipe_map, j) {
1188                         tile = pipe->tile;
1189                         if (tile->display)
1190                                 continue;
1191
1192                         assert(!tile->parent);
1193
1194                         display = grdev_find_display(session, pipe->name);
1195                         if (display && display->tile) {
1196                                 log_debug("grdev: %s/%s: occupied display for pipe %s",
1197                                           session->name, card->name, pipe->name);
1198                                 continue;
1199                         } else if (!display) {
1200                                 r = grdev_display_new(&display, session, pipe->name);
1201                                 if (r < 0) {
1202                                         log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
1203                                                   session->name, card->name, pipe->name, strerror(-r));
1204                                         continue;
1205                                 }
1206                         }
1207
1208                         tile_link(pipe->tile, display->tile);
1209                 }
1210         }
1211
1212         /* update displays */
1213         HASHMAP_FOREACH(display, session->display_map, i)
1214                 session_change_display(session, display);
1215 }
1216
1217 grdev_session *grdev_session_pin(grdev_session *session) {
1218         assert(session);
1219
1220         ++session->n_pins;
1221         return session;
1222 }
1223
1224 grdev_session *grdev_session_unpin(grdev_session *session) {
1225         if (!session)
1226                 return NULL;
1227
1228         assert(session->n_pins > 0);
1229
1230         if (--session->n_pins == 0)
1231                 session_configure(session);
1232
1233         return NULL;
1234 }
1235
1236 /*
1237  * Contexts
1238  */
1239
1240 int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
1241         _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
1242
1243         assert_return(out, -EINVAL);
1244         assert_return(event, -EINVAL);
1245
1246         context = new0(grdev_context, 1);
1247         if (!context)
1248                 return -ENOMEM;
1249
1250         context->ref = 1;
1251         context->event = sd_event_ref(event);
1252
1253         if (sysbus)
1254                 context->sysbus = sd_bus_ref(sysbus);
1255
1256         context->session_map = hashmap_new(&string_hash_ops);
1257         if (!context->session_map)
1258                 return -ENOMEM;
1259
1260         *out = context;
1261         context = NULL;
1262         return 0;
1263 }
1264
1265 static void context_cleanup(grdev_context *context) {
1266         assert(hashmap_size(context->session_map) == 0);
1267
1268         hashmap_free(context->session_map);
1269         context->sysbus = sd_bus_unref(context->sysbus);
1270         context->event = sd_event_unref(context->event);
1271         free(context);
1272 }
1273
1274 grdev_context *grdev_context_ref(grdev_context *context) {
1275         assert_return(context, NULL);
1276         assert_return(context->ref > 0, NULL);
1277
1278         ++context->ref;
1279         return context;
1280 }
1281
1282 grdev_context *grdev_context_unref(grdev_context *context) {
1283         if (!context)
1284                 return NULL;
1285
1286         assert_return(context->ref > 0, NULL);
1287
1288         if (--context->ref == 0)
1289                 context_cleanup(context);
1290
1291         return NULL;
1292 }