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