chiark / gitweb /
terminal: forward DEVICE_CHANGE events via sysview
[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;
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;
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;
1134         dev_t devnum;
1135
1136         assert(session);
1137         assert(ud);
1138
1139         devnum = udev_device_get_devnum(ud);
1140         if (devnum == 0)
1141                 return;
1142
1143         card = grdev_find_drm_card(session, devnum);
1144         if (!card)
1145                 return;
1146
1147         /* TODO: hotplug card */
1148 }
1149
1150 static void session_configure(grdev_session *session) {
1151         grdev_display *display;
1152         grdev_tile *tile;
1153         grdev_card *card;
1154         grdev_pipe *pipe;
1155         Iterator i, j;
1156         int r;
1157
1158         assert(session);
1159
1160         /*
1161          * Whenever backends add or remove pipes, we set session->modified and
1162          * require them to pin the session while modifying it. On release, we
1163          * reconfigure the device and re-assign displays to all modified pipes.
1164          *
1165          * So far, we configure each pipe as a separate display. We do not
1166          * support user-configuration, nor have we gotten any reports from
1167          * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
1168          * we get reports, we keep the logic to a minimum.
1169          */
1170
1171         /* create new displays for all unconfigured pipes */
1172         HASHMAP_FOREACH(card, session->card_map, i) {
1173                 if (!card->modified)
1174                         continue;
1175
1176                 card->modified = false;
1177
1178                 HASHMAP_FOREACH(pipe, card->pipe_map, j) {
1179                         tile = pipe->tile;
1180                         if (tile->display)
1181                                 continue;
1182
1183                         assert(!tile->parent);
1184
1185                         display = grdev_find_display(session, pipe->name);
1186                         if (display && display->tile) {
1187                                 log_debug("grdev: %s/%s: occupied display for pipe %s",
1188                                           session->name, card->name, pipe->name);
1189                                 continue;
1190                         } else if (!display) {
1191                                 r = grdev_display_new(&display, session, pipe->name);
1192                                 if (r < 0) {
1193                                         log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
1194                                                   session->name, card->name, pipe->name, strerror(-r));
1195                                         continue;
1196                                 }
1197                         }
1198
1199                         tile_link(pipe->tile, display->tile);
1200                 }
1201         }
1202
1203         /* update displays */
1204         HASHMAP_FOREACH(display, session->display_map, i)
1205                 session_change_display(session, display);
1206 }
1207
1208 grdev_session *grdev_session_pin(grdev_session *session) {
1209         assert(session);
1210
1211         ++session->n_pins;
1212         return session;
1213 }
1214
1215 grdev_session *grdev_session_unpin(grdev_session *session) {
1216         if (!session)
1217                 return NULL;
1218
1219         assert(session->n_pins > 0);
1220
1221         if (--session->n_pins == 0)
1222                 session_configure(session);
1223
1224         return NULL;
1225 }
1226
1227 /*
1228  * Contexts
1229  */
1230
1231 int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
1232         _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
1233
1234         assert_return(out, -EINVAL);
1235         assert_return(event, -EINVAL);
1236
1237         context = new0(grdev_context, 1);
1238         if (!context)
1239                 return -ENOMEM;
1240
1241         context->ref = 1;
1242         context->event = sd_event_ref(event);
1243
1244         if (sysbus)
1245                 context->sysbus = sd_bus_ref(sysbus);
1246
1247         context->session_map = hashmap_new(&string_hash_ops);
1248         if (!context->session_map)
1249                 return -ENOMEM;
1250
1251         *out = context;
1252         context = NULL;
1253         return 0;
1254 }
1255
1256 static void context_cleanup(grdev_context *context) {
1257         assert(hashmap_size(context->session_map) == 0);
1258
1259         hashmap_free(context->session_map);
1260         context->sysbus = sd_bus_unref(context->sysbus);
1261         context->event = sd_event_unref(context->event);
1262         free(context);
1263 }
1264
1265 grdev_context *grdev_context_ref(grdev_context *context) {
1266         assert_return(context, NULL);
1267         assert_return(context->ref > 0, NULL);
1268
1269         ++context->ref;
1270         return context;
1271 }
1272
1273 grdev_context *grdev_context_unref(grdev_context *context) {
1274         if (!context)
1275                 return NULL;
1276
1277         assert_return(context->ref > 0, NULL);
1278
1279         if (--context->ref == 0)
1280                 context_cleanup(context);
1281
1282         return NULL;
1283 }