uvtd: vt: implement VT_GETMODE/SETMODE ioctl state-tracking
[platform/upstream/kmscon.git] / src / kmscon_main.c
1 /*
2  * kmscon - KMS Console
3  *
4  * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 #include <errno.h>
27 #include <paths.h>
28 #include <signal.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/signalfd.h>
34 #include "conf.h"
35 #include "eloop.h"
36 #include "kmscon_conf.h"
37 #include "kmscon_module.h"
38 #include "kmscon_seat.h"
39 #include "shl_dlist.h"
40 #include "shl_log.h"
41 #include "shl_misc.h"
42 #include "text.h"
43 #include "uterm_input.h"
44 #include "uterm_monitor.h"
45 #include "uterm_video.h"
46 #include "uterm_vt.h"
47
48 struct app_video {
49         struct shl_dlist list;
50         struct app_seat *seat;
51         struct uterm_monitor_dev *udev;
52
53         char *node;
54         struct uterm_video *video;
55 };
56
57 struct app_seat {
58         struct shl_dlist list;
59         struct kmscon_app *app;
60         struct uterm_monitor_seat *useat;
61
62         bool awake;
63         char *name;
64         struct kmscon_seat *seat;
65         struct conf_ctx *conf_ctx;
66         struct kmscon_conf_t *conf;
67         struct shl_dlist videos;
68 };
69
70 struct kmscon_app {
71         struct conf_ctx *conf_ctx;
72         struct kmscon_conf_t *conf;
73         bool exiting;
74
75         struct ev_eloop *eloop;
76         unsigned int vt_exit_count;
77
78         struct uterm_vt_master *vtm;
79         struct uterm_monitor *mon;
80         struct shl_dlist seats;
81         unsigned int running_seats;
82 };
83
84 static int app_seat_event(struct kmscon_seat *s, unsigned int event,
85                           void *data)
86 {
87         struct app_seat *seat = data;
88         struct kmscon_app *app = seat->app;
89         struct shl_dlist *iter;
90         struct app_video *vid;
91
92         switch (event) {
93         case KMSCON_SEAT_FOREGROUND:
94                 seat->awake = true;
95
96                 shl_dlist_for_each(iter, &seat->videos) {
97                         vid = shl_dlist_entry(iter, struct app_video, list);
98                         uterm_video_wake_up(vid->video);
99                 }
100                 break;
101         case KMSCON_SEAT_BACKGROUND:
102                 shl_dlist_for_each(iter, &seat->videos) {
103                         vid = shl_dlist_entry(iter, struct app_video, list);
104                         uterm_video_sleep(vid->video);
105                 }
106
107                 seat->awake = false;
108                 break;
109         case KMSCON_SEAT_SLEEP:
110                 if (app->vt_exit_count > 0) {
111                         log_debug("deactivating VT on exit, %d to go",
112                                   app->vt_exit_count - 1);
113                         if (!--app->vt_exit_count)
114                                 ev_eloop_exit(app->eloop);
115                 }
116                 break;
117         case KMSCON_SEAT_WAKE_UP:
118                 if (app->exiting)
119                         return -EBUSY;
120                 break;
121         case KMSCON_SEAT_HUP:
122                 kmscon_seat_free(seat->seat);
123                 seat->seat = NULL;
124
125                 if (!app->conf->listen) {
126                         --app->running_seats;
127                         if (!app->running_seats) {
128                                 log_debug("seat HUP on %s in default-mode; exiting...",
129                                           seat->name);
130                                 ev_eloop_exit(app->eloop);
131                         } else {
132                                 log_debug("seat HUP on %s in default-mode; %u more running seats",
133                                           seat->name, app->running_seats);
134                         }
135                 } else {
136                         /* Seat HUP here means that we are running in
137                          * listen-mode on a modular-VT like kmscon-fake-VTs. But
138                          * this is an invalid setup. In listen-mode we
139                          * exclusively run as seat-VT-master without a
140                          * controlling VT and we effectively prevent other
141                          * setups during startup. Hence, we can safely drop the
142                          * seat here and ignore it.
143                          * You can destroy and recreate the seat to make kmscon
144                          * pick it up again in listen-mode. */
145                         log_warning("seat HUP on %s in listen-mode; dropping seat...",
146                                     seat->name);
147                 }
148
149                 break;
150         }
151
152         return 0;
153 }
154
155 static int app_seat_new(struct kmscon_app *app, const char *sname,
156                         struct uterm_monitor_seat *useat)
157 {
158         struct app_seat *seat;
159         int ret;
160         unsigned int i, types;
161         bool found;
162         char *cseat;
163
164         if (app->exiting)
165                 return -EBUSY;
166
167         found = false;
168         if (kmscon_conf_is_all_seats(app->conf)) {
169                 found = true;
170         } else if (kmscon_conf_is_current_seat(app->conf)) {
171                 cseat = getenv("XDG_SEAT");
172                 if (!cseat)
173                         cseat = "seat0";
174                 if (!strcmp(cseat, sname))
175                         found = true;
176         } else {
177                 for (i = 0; app->conf->seats[i]; ++i) {
178                         if (!strcmp(app->conf->seats[i], sname)) {
179                                 found = true;
180                                 break;
181                         }
182                 }
183         }
184
185         if (!found) {
186                 log_info("ignoring new seat %s as not specified in seat-list",
187                          sname);
188                 return -ERANGE;
189         }
190
191         log_debug("new seat %s", sname);
192
193         seat = malloc(sizeof(*seat));
194         if (!seat) {
195                 log_error("cannot allocate memory for seat %s", sname);
196                 return -ENOMEM;
197         }
198         memset(seat, 0, sizeof(*seat));
199         seat->app = app;
200         seat->useat = useat;
201         shl_dlist_init(&seat->videos);
202
203         seat->name = strdup(sname);
204         if (!seat->name) {
205                 log_error("cannot copy seat name on seat %s", sname);
206                 ret = -ENOMEM;
207                 goto err_free;
208         }
209
210         types = UTERM_VT_FAKE;
211         if (!app->conf->listen)
212                 types |= UTERM_VT_REAL;
213
214         ret = kmscon_seat_new(&seat->seat, app->conf_ctx, app->eloop, app->vtm,
215                               types, sname, app_seat_event, seat);
216         if (ret) {
217                 if (ret == -ERANGE)
218                         log_debug("ignoring seat %s as it already has a seat manager",
219                                   sname);
220                 else
221                         log_error("cannot create seat object on seat %s: %d",
222                                   sname, ret);
223                 goto err_name;
224         }
225         seat->conf_ctx = kmscon_seat_get_conf(seat->seat);
226         seat->conf = conf_ctx_get_mem(seat->conf_ctx);
227
228         uterm_monitor_set_seat_data(seat->useat, seat);
229         shl_dlist_link(&app->seats, &seat->list);
230         ++app->running_seats;
231
232         kmscon_seat_startup(seat->seat);
233
234         return 0;
235
236 err_name:
237         free(seat->name);
238 err_free:
239         free(seat);
240         return ret;
241 }
242
243 static void app_seat_free(struct app_seat *seat)
244 {
245         log_debug("free seat %s", seat->name);
246
247         shl_dlist_unlink(&seat->list);
248         uterm_monitor_set_seat_data(seat->useat, NULL);
249         kmscon_seat_free(seat->seat);
250         free(seat->name);
251         free(seat);
252 }
253
254 static void app_seat_video_event(struct uterm_video *video,
255                                  struct uterm_video_hotplug *ev,
256                                  void *data)
257 {
258         struct app_video *vid = data;
259
260         switch (ev->action) {
261         case UTERM_NEW:
262                 if (!vid->seat->app->exiting)
263                         kmscon_seat_add_display(vid->seat->seat, ev->display);
264                 break;
265         case UTERM_GONE:
266                 kmscon_seat_remove_display(vid->seat->seat, ev->display);
267                 break;
268         }
269 }
270
271 static bool app_seat_gpu_is_ignored(struct app_seat *seat,
272                                     unsigned int type,
273                                     bool drm_backed,
274                                     bool primary,
275                                     bool aux,
276                                     const char *node)
277 {
278         switch (type) {
279         case UTERM_MONITOR_FBDEV:
280                 if (seat->conf->drm) {
281                         if (drm_backed) {
282                                 log_info("ignoring video device %s on seat %s as it is a DRM-fbdev device",
283                                          node, seat->name);
284                                 return true;
285                         }
286                 }
287                 break;
288         case UTERM_MONITOR_DRM:
289                 if (!seat->conf->drm) {
290                         log_info("ignoring video device %s on seat %s as it is a DRM device",
291                                   node, seat->name);
292                         return true;
293                 }
294                 break;
295         default:
296                 log_info("ignoring unknown video device %s on seat %s",
297                          node, seat->name);
298                 return true;
299         }
300
301         if (seat->conf->gpus == KMSCON_GPU_PRIMARY && !primary) {
302                 log_info("ignoring video device %s on seat %s as it is no primary GPU",
303                          node, seat->name);
304                 return true;
305         }
306
307         if (seat->conf->gpus == KMSCON_GPU_AUX && !primary && !aux) {
308                 log_info("ignoring video device %s on seat %s as it is neither a primary nor auxiliary GPU",
309                          node, seat->name);
310                 return true;
311         }
312
313         return false;
314 }
315
316 static int app_seat_add_video(struct app_seat *seat,
317                               unsigned int type,
318                               unsigned int flags,
319                               const char *node,
320                               struct uterm_monitor_dev *udev)
321 {
322         int ret;
323         const struct uterm_video_module *mode;
324         struct app_video *vid;
325
326         if (seat->app->exiting)
327                 return -EBUSY;
328
329         if (app_seat_gpu_is_ignored(seat, type,
330                                     flags & UTERM_MONITOR_DRM_BACKED,
331                                     flags & UTERM_MONITOR_PRIMARY,
332                                     flags & UTERM_MONITOR_AUX,
333                                     node))
334                 return -ERANGE;
335
336         log_debug("new video device %s on seat %s", node, seat->name);
337
338         vid = malloc(sizeof(*vid));
339         if (!vid) {
340                 log_error("cannot allocate memory for video device %s on seat %s",
341                           node, seat->name);
342                 return -ENOMEM;
343         }
344         memset(vid, 0, sizeof(*vid));
345         vid->seat = seat;
346         vid->udev = udev;
347
348         vid->node = strdup(node);
349         if (!vid->node) {
350                 log_error("cannot copy video device name %s on seat %s",
351                           node, seat->name);
352                 ret = -ENOMEM;
353                 goto err_free;
354         }
355
356         if (type == UTERM_MONITOR_DRM) {
357                 if (seat->conf->hwaccel)
358                         mode = UTERM_VIDEO_DRM3D;
359                 else
360                         mode = UTERM_VIDEO_DRM2D;
361         } else {
362                 mode = UTERM_VIDEO_FBDEV;
363         }
364
365         ret = uterm_video_new(&vid->video, seat->app->eloop, node, mode);
366         if (ret) {
367                 if (mode == UTERM_VIDEO_DRM3D) {
368                         log_info("cannot create drm3d device %s on seat %s (%d); trying drm2d mode",
369                                  vid->node, seat->name, ret);
370                         ret = uterm_video_new(&vid->video, seat->app->eloop,
371                                               node, UTERM_VIDEO_DRM2D);
372                         if (ret)
373                                 goto err_node;
374                 } else {
375                         goto err_node;
376                 }
377         }
378
379         ret = uterm_video_register_cb(vid->video, app_seat_video_event, vid);
380         if (ret) {
381                 log_error("cannot register video callback for device %s on seat %s: %d",
382                           vid->node, seat->name, ret);
383                 goto err_video;
384         }
385
386         if (seat->awake)
387                 uterm_video_wake_up(vid->video);
388
389         uterm_monitor_set_dev_data(vid->udev, vid);
390         shl_dlist_link(&seat->videos, &vid->list);
391         return 0;
392
393 err_video:
394         uterm_video_unref(vid->video);
395 err_node:
396         free(vid->node);
397 err_free:
398         free(vid);
399         return ret;
400 }
401
402 static void app_seat_remove_video(struct app_seat *seat, struct app_video *vid)
403 {
404         struct uterm_display *disp;
405
406         log_debug("free video device %s on seat %s", vid->node, seat->name);
407
408         shl_dlist_unlink(&vid->list);
409         uterm_monitor_set_dev_data(vid->udev, NULL);
410         uterm_video_unregister_cb(vid->video, app_seat_video_event, vid);
411
412         disp = uterm_video_get_displays(vid->video);
413         while (disp) {
414                 kmscon_seat_remove_display(seat->seat, disp);
415                 disp = uterm_display_next(disp);
416         }
417
418         uterm_video_unref(vid->video);
419         free(vid->node);
420         free(vid);
421 }
422
423 static void app_monitor_event(struct uterm_monitor *mon,
424                               struct uterm_monitor_event *ev,
425                               void *data)
426 {
427         struct kmscon_app *app = data;
428         struct app_seat *seat;
429         struct app_video *vid;
430         int ret;
431
432         switch (ev->type) {
433         case UTERM_MONITOR_NEW_SEAT:
434                 ret = app_seat_new(app, ev->seat_name, ev->seat);
435                 if (ret)
436                         return;
437                 break;
438         case UTERM_MONITOR_FREE_SEAT:
439                 if (ev->seat_data)
440                         app_seat_free(ev->seat_data);
441                 break;
442         case UTERM_MONITOR_NEW_DEV:
443                 seat = ev->seat_data;
444                 if (!seat)
445                         return;
446
447                 switch (ev->dev_type) {
448                 case UTERM_MONITOR_DRM:
449                 case UTERM_MONITOR_FBDEV:
450                         ret = app_seat_add_video(seat, ev->dev_type,
451                                                  ev->dev_flags,
452                                                  ev->dev_node, ev->dev);
453                         if (ret)
454                                 return;
455                         break;
456                 case UTERM_MONITOR_INPUT:
457                         log_debug("new input device %s on seat %s",
458                                   ev->dev_node, seat->name);
459                         kmscon_seat_add_input(seat->seat, ev->dev_node);
460                         break;
461                 }
462                 break;
463         case UTERM_MONITOR_FREE_DEV:
464                 seat = ev->seat_data;
465                 if (!seat)
466                         return;
467
468                 switch (ev->dev_type) {
469                 case UTERM_MONITOR_DRM:
470                 case UTERM_MONITOR_FBDEV:
471                         if (ev->dev_data)
472                                 app_seat_remove_video(seat, ev->dev_data);
473                         break;
474                 case UTERM_MONITOR_INPUT:
475                         log_debug("free input device %s on seat %s",
476                                   ev->dev_node, seat->name);
477                         kmscon_seat_remove_input(seat->seat, ev->dev_node);
478                         break;
479                 }
480                 break;
481         case UTERM_MONITOR_HOTPLUG_DEV:
482                 seat = ev->seat_data;
483                 if (!seat)
484                         return;
485
486                 switch (ev->dev_type) {
487                 case UTERM_MONITOR_DRM:
488                 case UTERM_MONITOR_FBDEV:
489                         vid = ev->dev_data;
490                         if (!vid)
491                                 return;
492
493                         log_debug("video hotplug event on device %s on seat %s",
494                                   vid->node, seat->name);
495                         uterm_video_poll(vid->video);
496                         break;
497                 }
498                 break;
499         }
500 }
501
502 static void app_sig_generic(struct ev_eloop *eloop,
503                             struct signalfd_siginfo *info,
504                             void *data)
505 {
506         struct kmscon_app *app = data;
507
508         log_info("terminating due to caught signal %d", info->ssi_signo);
509         ev_eloop_exit(app->eloop);
510 }
511
512 static void app_sig_ignore(struct ev_eloop *eloop,
513                            struct signalfd_siginfo *info,
514                            void *data)
515 {
516 }
517
518 static void destroy_app(struct kmscon_app *app)
519 {
520         uterm_monitor_unref(app->mon);
521         uterm_vt_master_unref(app->vtm);
522         ev_eloop_unregister_signal_cb(app->eloop, SIGPIPE, app_sig_ignore,
523                                       app);
524         ev_eloop_unregister_signal_cb(app->eloop, SIGINT, app_sig_generic,
525                                       app);
526         ev_eloop_unregister_signal_cb(app->eloop, SIGTERM, app_sig_generic,
527                                       app);
528         ev_eloop_unref(app->eloop);
529 }
530
531 static int setup_app(struct kmscon_app *app)
532 {
533         int ret;
534
535         shl_dlist_init(&app->seats);
536
537         ret = ev_eloop_new(&app->eloop, log_llog, NULL);
538         if (ret) {
539                 log_error("cannot create eloop object: %d", ret);
540                 goto err_app;
541         }
542
543         ret = ev_eloop_register_signal_cb(app->eloop, SIGTERM,
544                                           app_sig_generic, app);
545         if (ret) {
546                 log_error("cannot register SIGTERM signal handler: %d", ret);
547                 goto err_app;
548         }
549
550         ret = ev_eloop_register_signal_cb(app->eloop, SIGINT,
551                                           app_sig_generic, app);
552         if (ret) {
553                 log_error("cannot register SIGINT signal handler: %d", ret);
554                 goto err_app;
555         }
556
557         ret = ev_eloop_register_signal_cb(app->eloop, SIGPIPE,
558                                           app_sig_ignore, app);
559         if (ret) {
560                 log_error("cannot register SIGPIPE signal handler: %d", ret);
561                 goto err_app;
562         }
563
564         ret = uterm_vt_master_new(&app->vtm, app->eloop);
565         if (ret) {
566                 log_error("cannot create VT master: %d", ret);
567                 goto err_app;
568         }
569
570         ret = uterm_monitor_new(&app->mon, app->eloop, app_monitor_event, app);
571         if (ret) {
572                 log_error("cannot create device monitor: %d", ret);
573                 goto err_app;
574         }
575
576         log_debug("scanning for devices...");
577         uterm_monitor_scan(app->mon);
578
579         return 0;
580
581 err_app:
582         destroy_app(app);
583         return ret;
584 }
585
586 int main(int argc, char **argv)
587 {
588         int ret;
589         struct conf_ctx *conf_ctx;
590         struct kmscon_conf_t *conf;
591         struct kmscon_app app;
592
593         ret = kmscon_conf_new(&conf_ctx);
594         if (ret) {
595                 log_error("cannot create configuration: %d", ret);
596                 goto err_out;
597         }
598         conf = conf_ctx_get_mem(conf_ctx);
599
600         ret = kmscon_conf_load_main(conf_ctx, argc, argv);
601         if (ret) {
602                 log_error("cannot load configuration: %d", ret);
603                 goto err_conf;
604         }
605
606         if (conf->exit) {
607                 kmscon_conf_free(conf_ctx);
608                 return 0;
609         }
610
611         kmscon_load_modules();
612         kmscon_font_register(&kmscon_font_8x16_ops);
613         kmscon_text_register(&kmscon_text_bblit_ops);
614
615         memset(&app, 0, sizeof(app));
616         app.conf_ctx = conf_ctx;
617         app.conf = conf;
618
619         ret = setup_app(&app);
620         if (ret)
621                 goto err_unload;
622
623         if (!app.conf->listen && !app.running_seats) {
624                 log_notice("no running seats; exiting");
625         } else {
626                 log_debug("%u running seats after startup", app.running_seats);
627                 ev_eloop_run(app.eloop, -1);
628         }
629
630         app.exiting = true;
631
632         if (app.conf->switchvt) {
633                 /* The VT subsystem needs to acknowledge the VT-leave so if it
634                  * returns -EINPROGRESS we need to wait for the VT-leave SIGUSR2
635                  * signal to arrive. Therefore, we use a separate eloop object
636                  * which is used by the VT system only. Therefore, waiting on
637                  * this eloop allows us to safely wait 50ms for the SIGUSR2 to
638                  * arrive.
639                  * We use a timeout of 100ms to avoid hanging on exit. */
640                 log_debug("deactivating VTs during shutdown");
641                 ret = uterm_vt_master_deactivate_all(app.vtm);
642                 if (ret > 0) {
643                         log_debug("waiting for %d VTs to deactivate", ret);
644                         app.vt_exit_count = ret;
645                         ev_eloop_run(app.eloop, 50);
646                 }
647         }
648
649         ret = 0;
650
651         destroy_app(&app);
652 err_unload:
653         kmscon_text_unregister(kmscon_text_bblit_ops.name);
654         kmscon_font_unregister(kmscon_font_8x16_ops.name);
655         kmscon_unload_modules();
656 err_conf:
657         kmscon_conf_free(conf_ctx);
658 err_out:
659         if (ret)
660                 log_err("cannot initialize kmscon, errno %d: %s",
661                         ret, strerror(-ret));
662         log_info("exiting");
663         return -ret;
664 }