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