chiark / gitweb /
terminal: grdev: refresh device state on hotplug events
[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         else if (changed)
929                 session_raise_display_change(session, display);
930         else if (display->framed)
931                 session_frame(session, display);
932 }
933
934 static void session_frame(grdev_session *session, grdev_display *display) {
935         assert(session);
936         assert(display);
937
938         display->framed = false;
939
940         if (!display->enabled || !session->enabled)
941                 return;
942
943         if (session->n_pins > 0)
944                 display->framed = true;
945         else
946                 session_raise_display_frame(session, display);
947 }
948
949 int grdev_session_new(grdev_session **out,
950                       grdev_context *context,
951                       unsigned int flags,
952                       const char *name,
953                       grdev_event_fn event_fn,
954                       void *userdata) {
955         _cleanup_(grdev_session_freep) grdev_session *session = NULL;
956         int r;
957
958         assert(out);
959         assert(context);
960         assert(name);
961         assert(event_fn);
962         assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL);
963         assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL);
964         assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL);
965
966         session = new0(grdev_session, 1);
967         if (!session)
968                 return -ENOMEM;
969
970         session->context = grdev_context_ref(context);
971         session->custom = flags & GRDEV_SESSION_CUSTOM;
972         session->managed = flags & GRDEV_SESSION_MANAGED;
973         session->event_fn = event_fn;
974         session->userdata = userdata;
975
976         session->name = strdup(name);
977         if (!session->name)
978                 return -ENOMEM;
979
980         if (session->managed) {
981                 r = sd_bus_path_encode("/org/freedesktop/login1/session",
982                                        session->name, &session->path);
983                 if (r < 0)
984                         return r;
985         }
986
987         session->card_map = hashmap_new(&string_hash_ops);
988         if (!session->card_map)
989                 return -ENOMEM;
990
991         session->display_map = hashmap_new(&string_hash_ops);
992         if (!session->display_map)
993                 return -ENOMEM;
994
995         r = hashmap_put(context->session_map, session->name, session);
996         if (r < 0)
997                 return r;
998
999         *out = session;
1000         session = NULL;
1001         return 0;
1002 }
1003
1004 grdev_session *grdev_session_free(grdev_session *session) {
1005         grdev_card *card;
1006
1007         if (!session)
1008                 return NULL;
1009
1010         grdev_session_disable(session);
1011
1012         while ((card = hashmap_first(session->card_map)))
1013                 session_remove_card(session, card);
1014
1015         assert(hashmap_size(session->display_map) == 0);
1016
1017         if (session->name)
1018                 hashmap_remove_value(session->context->session_map, session->name, session);
1019
1020         hashmap_free(session->display_map);
1021         hashmap_free(session->card_map);
1022         session->context = grdev_context_unref(session->context);
1023         free(session->path);
1024         free(session->name);
1025         free(session);
1026
1027         return NULL;
1028 }
1029
1030 bool grdev_session_is_enabled(grdev_session *session) {
1031         return session && session->enabled;
1032 }
1033
1034 void grdev_session_enable(grdev_session *session) {
1035         grdev_card *card;
1036         Iterator iter;
1037
1038         assert(session);
1039
1040         if (!session->enabled) {
1041                 session->enabled = true;
1042                 HASHMAP_FOREACH(card, session->card_map, iter)
1043                         grdev_card_enable(card);
1044         }
1045 }
1046
1047 void grdev_session_disable(grdev_session *session) {
1048         grdev_card *card;
1049         Iterator iter;
1050
1051         assert(session);
1052
1053         if (session->enabled) {
1054                 session->enabled = false;
1055                 HASHMAP_FOREACH(card, session->card_map, iter)
1056                         grdev_card_disable(card);
1057         }
1058 }
1059
1060 void grdev_session_commit(grdev_session *session) {
1061         grdev_card *card;
1062         Iterator iter;
1063
1064         assert(session);
1065
1066         if (!session->enabled)
1067                 return;
1068
1069         HASHMAP_FOREACH(card, session->card_map, iter)
1070                 if (card->vtable->commit)
1071                         card->vtable->commit(card);
1072 }
1073
1074 void grdev_session_restore(grdev_session *session) {
1075         grdev_card *card;
1076         Iterator iter;
1077
1078         assert(session);
1079
1080         if (!session->enabled)
1081                 return;
1082
1083         HASHMAP_FOREACH(card, session->card_map, iter)
1084                 if (card->vtable->restore)
1085                         card->vtable->restore(card);
1086 }
1087
1088 void grdev_session_add_drm(grdev_session *session, struct udev_device *ud) {
1089         grdev_card *card;
1090         dev_t devnum;
1091         int r;
1092
1093         assert(session);
1094         assert(ud);
1095
1096         devnum = udev_device_get_devnum(ud);
1097         if (devnum == 0)
1098                 return grdev_session_hotplug_drm(session, ud);
1099
1100         card = grdev_find_drm_card(session, devnum);
1101         if (card)
1102                 return;
1103
1104         r = grdev_drm_card_new(&card, session, ud);
1105         if (r < 0) {
1106                 log_debug("grdev: %s: cannot add DRM device for %s: %s",
1107                           session->name, udev_device_get_syspath(ud), strerror(-r));
1108                 return;
1109         }
1110
1111         session_add_card(session, card);
1112 }
1113
1114 void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud) {
1115         grdev_card *card;
1116         dev_t devnum;
1117
1118         assert(session);
1119         assert(ud);
1120
1121         devnum = udev_device_get_devnum(ud);
1122         if (devnum == 0)
1123                 return grdev_session_hotplug_drm(session, ud);
1124
1125         card = grdev_find_drm_card(session, devnum);
1126         if (!card)
1127                 return;
1128
1129         session_remove_card(session, card);
1130 }
1131
1132 void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud) {
1133         grdev_card *card = NULL;
1134         struct udev_device *p;
1135         dev_t devnum;
1136
1137         assert(session);
1138         assert(ud);
1139
1140         for (p = ud; p; p = udev_device_get_parent_with_subsystem_devtype(p, "drm", NULL)) {
1141                 devnum = udev_device_get_devnum(ud);
1142                 if (devnum == 0)
1143                         continue;
1144
1145                 card = grdev_find_drm_card(session, devnum);
1146                 if (card)
1147                         break;
1148         }
1149
1150         if (!card)
1151                 return;
1152
1153         grdev_drm_card_hotplug(card, ud);
1154 }
1155
1156 static void session_configure(grdev_session *session) {
1157         grdev_display *display;
1158         grdev_tile *tile;
1159         grdev_card *card;
1160         grdev_pipe *pipe;
1161         Iterator i, j;
1162         int r;
1163
1164         assert(session);
1165
1166         /*
1167          * Whenever backends add or remove pipes, we set session->modified and
1168          * require them to pin the session while modifying it. On release, we
1169          * reconfigure the device and re-assign displays to all modified pipes.
1170          *
1171          * So far, we configure each pipe as a separate display. We do not
1172          * support user-configuration, nor have we gotten any reports from
1173          * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
1174          * we get reports, we keep the logic to a minimum.
1175          */
1176
1177         /* create new displays for all unconfigured pipes */
1178         HASHMAP_FOREACH(card, session->card_map, i) {
1179                 if (!card->modified)
1180                         continue;
1181
1182                 card->modified = false;
1183
1184                 HASHMAP_FOREACH(pipe, card->pipe_map, j) {
1185                         tile = pipe->tile;
1186                         if (tile->display)
1187                                 continue;
1188
1189                         assert(!tile->parent);
1190
1191                         display = grdev_find_display(session, pipe->name);
1192                         if (display && display->tile) {
1193                                 log_debug("grdev: %s/%s: occupied display for pipe %s",
1194                                           session->name, card->name, pipe->name);
1195                                 continue;
1196                         } else if (!display) {
1197                                 r = grdev_display_new(&display, session, pipe->name);
1198                                 if (r < 0) {
1199                                         log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
1200                                                   session->name, card->name, pipe->name, strerror(-r));
1201                                         continue;
1202                                 }
1203                         }
1204
1205                         tile_link(pipe->tile, display->tile);
1206                 }
1207         }
1208
1209         /* update displays */
1210         HASHMAP_FOREACH(display, session->display_map, i)
1211                 session_change_display(session, display);
1212 }
1213
1214 grdev_session *grdev_session_pin(grdev_session *session) {
1215         assert(session);
1216
1217         ++session->n_pins;
1218         return session;
1219 }
1220
1221 grdev_session *grdev_session_unpin(grdev_session *session) {
1222         if (!session)
1223                 return NULL;
1224
1225         assert(session->n_pins > 0);
1226
1227         if (--session->n_pins == 0)
1228                 session_configure(session);
1229
1230         return NULL;
1231 }
1232
1233 /*
1234  * Contexts
1235  */
1236
1237 int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
1238         _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
1239
1240         assert_return(out, -EINVAL);
1241         assert_return(event, -EINVAL);
1242
1243         context = new0(grdev_context, 1);
1244         if (!context)
1245                 return -ENOMEM;
1246
1247         context->ref = 1;
1248         context->event = sd_event_ref(event);
1249
1250         if (sysbus)
1251                 context->sysbus = sd_bus_ref(sysbus);
1252
1253         context->session_map = hashmap_new(&string_hash_ops);
1254         if (!context->session_map)
1255                 return -ENOMEM;
1256
1257         *out = context;
1258         context = NULL;
1259         return 0;
1260 }
1261
1262 static void context_cleanup(grdev_context *context) {
1263         assert(hashmap_size(context->session_map) == 0);
1264
1265         hashmap_free(context->session_map);
1266         context->sysbus = sd_bus_unref(context->sysbus);
1267         context->event = sd_event_unref(context->event);
1268         free(context);
1269 }
1270
1271 grdev_context *grdev_context_ref(grdev_context *context) {
1272         assert_return(context, NULL);
1273         assert_return(context->ref > 0, NULL);
1274
1275         ++context->ref;
1276         return context;
1277 }
1278
1279 grdev_context *grdev_context_unref(grdev_context *context) {
1280         if (!context)
1281                 return NULL;
1282
1283         assert_return(context->ref > 0, NULL);
1284
1285         if (--context->ref == 0)
1286                 context_cleanup(context);
1287
1288         return NULL;
1289 }