uvtd: vt: implement VT_GETMODE/SETMODE ioctl state-tracking
[platform/upstream/kmscon.git] / src / uvtd_seat.c
1 /*
2  * uvtd - User-space VT daemon
3  *
4  * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.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 /*
27  * Seats
28  * Each set of input+output devices form a single seat. Each seat is independent
29  * of each other and there can be exactly one user per seat interacting with the
30  * system.
31  * Per seat, we have multiple sessions. But only one session can be active at a
32  * time per seat. We allow external sessions, so session activation/deactivation
33  * may be asynchronous.
34  *
35  * A seat object manages all the sessions for a single seat. As long as a seat
36  * is asleep, no session is active. If you wake it up, the seat manager
37  * automatically schedules a session. You can then request other sessions to be
38  * scheduled and the seat manager will try to deactivate the current session and
39  * reactivate the new session.
40  *
41  * Note that session deactivation may be asynchronous (unless forced). So some
42  * calls might return -EINPROGRESS if the session-deactivation is pending. This
43  * shouldn't bother the user as the session will notify back soon that the
44  * deactivation was successfull. However, if it doesn't the user can chose to
45  * perform any other action and we will retry the operation. As a last resort,
46  * you can always kill the session by unregistering it or forcing a
47  * deactivation.
48  * "async_schedule" tracks the task that requested the deactivation of a
49  * session. So when the session notifies us that it got deactivated, we know
50  * what the user wanted and can perform the requested task now.
51  */
52
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include "eloop.h"
57 #include "shl_dlist.h"
58 #include "shl_log.h"
59 #include "uvtd_seat.h"
60
61 #define LOG_SUBSYSTEM "seat"
62
63 struct uvtd_session {
64         struct shl_dlist list;
65         unsigned long ref;
66         struct uvtd_seat *seat;
67         unsigned int id;
68
69         bool enabled;
70         bool deactivating;
71
72         uvtd_session_cb_t cb;
73         void *data;
74 };
75
76 /* task that requested the pending session-deactivation */
77 enum uvtd_async_schedule {
78         SCHEDULE_NONE,                  /* default, causes a reschedule */
79         SCHEDULE_SWITCH,                /* causes a reschedule */
80         SCHEDULE_SLEEP,                 /* puts the seat asleep */
81         SCHEDULE_UNREGISTER,            /* unregisters the session */
82 };
83
84 struct uvtd_seat {
85         struct ev_eloop *eloop;
86         char *name;
87
88         size_t session_count;
89         struct shl_dlist sessions;
90
91         bool awake;
92         struct uvtd_session *current_sess;
93         struct uvtd_session *scheduled_sess;
94         struct uvtd_session *dummy_sess;
95
96         unsigned int async_schedule;
97
98         uvtd_seat_cb_t cb;
99         void *data;
100 };
101
102 static int session_call(struct uvtd_session *sess, unsigned int event)
103 {
104         if (!sess->cb)
105                 return 0;
106
107         return sess->cb(sess, event, sess->data);
108 }
109
110 static int session_call_activate(struct uvtd_session *sess)
111 {
112         log_debug("activate session %p", sess);
113         return session_call(sess, UVTD_SESSION_ACTIVATE);
114 }
115
116 static int session_call_deactivate(struct uvtd_session *sess)
117 {
118         log_debug("deactivate session %p", sess);
119         return session_call(sess, UVTD_SESSION_DEACTIVATE);
120 }
121
122 /* drop the current session as if it was successfully deactivated */
123 static void seat_yield(struct uvtd_seat *seat)
124 {
125         if (!seat->current_sess)
126                 return;
127
128         seat->current_sess->deactivating = false;
129         seat->current_sess = NULL;
130         seat->async_schedule = SCHEDULE_NONE;
131 }
132
133 static int seat_go_asleep(struct uvtd_seat *seat, bool force)
134 {
135         int ret = 0;
136
137         if (!seat->awake)
138                 return 0;
139
140         if (seat->current_sess) {
141                 ret = -EBUSY;
142                 if (!force)
143                         return ret;
144
145                 seat_yield(seat);
146         }
147
148         seat->awake = false;
149
150         if (seat->cb)
151                 seat->cb(seat, UVTD_SEAT_SLEEP, seat->data);
152
153         return ret;
154 }
155
156 static void seat_go_awake(struct uvtd_seat *seat)
157 {
158         if (seat->awake)
159                 return;
160
161         seat->awake = true;
162 }
163
164 static int seat_run(struct uvtd_seat *seat)
165 {
166         int ret;
167         struct uvtd_session *session;
168
169         if (!seat->awake)
170                 return -EBUSY;
171         if (seat->current_sess)
172                 return 0;
173
174         if (!seat->scheduled_sess) {
175                 log_debug("no session scheduled to run (num: %zu)",
176                           seat->session_count);
177                 return -ENOENT;
178         }
179         session = seat->scheduled_sess;
180
181         /* TODO: unregister session and try next on failure */
182         ret = session_call_activate(session);
183         if (ret) {
184                 log_warning("cannot activate session %p: %d", session, ret);
185                 return ret;
186         }
187
188         seat->current_sess = session;
189
190         return 0;
191 }
192
193 static int seat_pause(struct uvtd_seat *seat, bool force, unsigned int async)
194 {
195         int ret;
196
197         if (!seat->current_sess)
198                 return 0;
199
200         /* TODO: pass \force to the session */
201         seat->current_sess->deactivating = true;
202         ret = session_call_deactivate(seat->current_sess);
203         if (ret) {
204                 if (!force && ret == -EINPROGRESS) {
205                         seat->async_schedule = async;
206                         log_debug("pending deactivation for session %p",
207                                   seat->current_sess);
208                 } else {
209                         log_warning("cannot deactivate session %p (%d): %d",
210                                     seat->current_sess, force, ret);
211                 }
212
213                 if (!force)
214                         return ret;
215         }
216
217         seat_yield(seat);
218         return ret;
219 }
220
221 static void seat_reschedule(struct uvtd_seat *seat)
222 {
223         struct shl_dlist *iter, *start;
224         struct uvtd_session *sess;
225
226         if (seat->scheduled_sess && seat->scheduled_sess->enabled)
227                 return;
228
229         if (seat->current_sess && seat->current_sess->enabled) {
230                 seat->scheduled_sess = seat->current_sess;
231                 return;
232         }
233
234         if (seat->current_sess)
235                 start = &seat->current_sess->list;
236         else
237                 start = &seat->sessions;
238
239         shl_dlist_for_each_but_one(iter, start, &seat->sessions) {
240                 sess = shl_dlist_entry(iter, struct uvtd_session, list);
241
242                 if (sess != seat->dummy_sess && sess->enabled) {
243                         seat->scheduled_sess = sess;
244                         return;
245                 }
246         }
247
248         if (seat->dummy_sess && seat->dummy_sess->enabled)
249                 seat->scheduled_sess = seat->dummy_sess;
250         else
251                 seat->scheduled_sess = NULL;
252 }
253
254 static bool seat_has_schedule(struct uvtd_seat *seat)
255 {
256         return seat->scheduled_sess &&
257                seat->scheduled_sess != seat->current_sess;
258 }
259
260 static int seat_switch(struct uvtd_seat *seat)
261 {
262         int ret;
263
264         ret = seat_pause(seat, false, SCHEDULE_SWITCH);
265         if (ret)
266                 return ret;
267
268         return seat_run(seat);
269 }
270
271 static void seat_schedule(struct uvtd_seat *seat, struct uvtd_session *sess)
272 {
273         seat->scheduled_sess = sess;
274         seat_reschedule(seat);
275         if (seat_has_schedule(seat))
276                 seat_switch(seat);
277 }
278
279 static void seat_next(struct uvtd_seat *seat, bool reverse)
280 {
281         struct shl_dlist *cur, *iter;
282         struct uvtd_session *s, *next;
283
284         if (seat->current_sess)
285                 cur = &seat->current_sess->list;
286         else if (seat->session_count)
287                 cur = &seat->sessions;
288         else
289                 return;
290
291         next = NULL;
292         if (!seat->current_sess && seat->dummy_sess &&
293             seat->dummy_sess->enabled)
294                 next = seat->dummy_sess;
295
296         if (reverse) {
297                 shl_dlist_for_each_reverse_but_one(iter, cur,
298                                                    &seat->sessions) {
299                         s = shl_dlist_entry(iter, struct uvtd_session, list);
300
301                         if (s->enabled && seat->dummy_sess != s) {
302                                 next = s;
303                                 break;
304                         }
305                 }
306         } else {
307                 shl_dlist_for_each_but_one(iter, cur, &seat->sessions) {
308                         s = shl_dlist_entry(iter, struct uvtd_session, list);
309
310                         if (s->enabled && seat->dummy_sess != s) {
311                                 next = s;
312                                 break;
313                         }
314                 }
315         }
316
317         if (!next)
318                 return;
319
320         seat_schedule(seat, next);
321 }
322
323 int uvtd_seat_new(struct uvtd_seat **out, const char *seatname,
324                   struct ev_eloop *eloop, uvtd_seat_cb_t cb, void *data)
325 {
326         struct uvtd_seat *seat;
327         int ret;
328
329         if (!out || !eloop || !seatname)
330                 return -EINVAL;
331
332         seat = malloc(sizeof(*seat));
333         if (!seat)
334                 return -ENOMEM;
335         memset(seat, 0, sizeof(*seat));
336         seat->eloop = eloop;
337         seat->cb = cb;
338         seat->data = data;
339         shl_dlist_init(&seat->sessions);
340
341         seat->name = strdup(seatname);
342         if (!seat->name) {
343                 ret = -ENOMEM;
344                 goto err_free;
345         }
346
347         ev_eloop_ref(seat->eloop);
348         *out = seat;
349         return 0;
350
351 err_free:
352         free(seat);
353         return ret;
354 }
355
356 void uvtd_seat_free(struct uvtd_seat *seat)
357 {
358         struct uvtd_session *s;
359         int ret;
360
361         if (!seat)
362                 return;
363
364         ret = seat_pause(seat, true, SCHEDULE_NONE);
365         if (ret)
366                 log_warning("destroying seat %s while session %p is active",
367                             seat->name, seat->current_sess);
368
369         ret = seat_go_asleep(seat, true);
370         if (ret)
371                 log_warning("destroying seat %s while still awake: %d",
372                             seat->name, ret);
373
374         while (!shl_dlist_empty(&seat->sessions)) {
375                 s = shl_dlist_entry(seat->sessions.next, struct uvtd_session,
376                                     list);
377                 uvtd_session_unregister(s);
378         }
379
380         free(seat->name);
381         ev_eloop_unref(seat->eloop);
382         free(seat);
383 }
384
385 const char *uvtd_seat_get_name(struct uvtd_seat *seat)
386 {
387         return seat ? seat->name : NULL;
388 }
389
390 struct ev_eloop *uvtd_seat_get_eloop(struct uvtd_seat *seat)
391 {
392         return seat ? seat->eloop : NULL;
393 }
394
395 int uvtd_seat_sleep(struct uvtd_seat *seat, bool force)
396 {
397         int ret, err = 0;
398
399         if (!seat)
400                 return -EINVAL;
401
402         ret = seat_pause(seat, force, SCHEDULE_SLEEP);
403         if (ret) {
404                 if (force)
405                         err = ret;
406                 else
407                         return ret;
408         }
409
410         ret = seat_go_asleep(seat, force);
411         if (ret) {
412                 if (force)
413                         err = ret;
414                 else
415                         return ret;
416         }
417
418         return err;
419 }
420
421 void uvtd_seat_wake_up(struct uvtd_seat *seat)
422 {
423         if (!seat)
424                 return;
425
426         seat_go_awake(seat);
427         seat_run(seat);
428 }
429
430 void uvtd_seat_schedule(struct uvtd_seat *seat, unsigned int id)
431 {
432         struct shl_dlist *iter;
433         struct uvtd_session *session;
434         unsigned int i;
435
436         if (!seat || !id)
437                 return;
438
439         session = NULL;
440         i = id;
441         shl_dlist_for_each(iter, &seat->sessions) {
442                 session = shl_dlist_entry(iter, struct uvtd_session, list);
443                 if (!--i)
444                         break;
445                 if (session->id >= id)
446                         break;
447         }
448
449         if (session)
450                 seat_schedule(seat, session);
451 }
452
453 int uvtd_seat_register_session(struct uvtd_seat *seat,
454                                struct uvtd_session **out,
455                                unsigned int id, uvtd_session_cb_t cb,
456                                void *data)
457 {
458         struct uvtd_session *sess, *s;
459         struct shl_dlist *iter;
460
461         if (!seat || !out)
462                 return -EINVAL;
463
464         sess = malloc(sizeof(*sess));
465         if (!sess)
466                 return -ENOMEM;
467
468         log_debug("register session %p with id %u on seat %p",
469                   sess, id, seat);
470
471         memset(sess, 0, sizeof(*sess));
472         sess->ref = 1;
473         sess->seat = seat;
474         sess->cb = cb;
475         sess->data = data;
476         sess->id = id;
477
478         ++seat->session_count;
479         *out = sess;
480
481         if (sess->id) {
482                 shl_dlist_for_each(iter, &seat->sessions) {
483                         s = shl_dlist_entry(iter, struct uvtd_session, list);
484                         if (!s->id || s->id > sess->id) {
485                                 shl_dlist_link_tail(iter, &sess->list);
486                                 return 0;
487                         }
488
489                         if (s->id == sess->id)
490                                 log_warning("session %p shadowed by %p",
491                                             sess, s);
492                 }
493         }
494
495         shl_dlist_link_tail(&seat->sessions, &sess->list);
496         return 0;
497 }
498
499 void uvtd_session_ref(struct uvtd_session *sess)
500 {
501         if (!sess || !sess->ref)
502                 return;
503
504         ++sess->ref;
505 }
506
507 void uvtd_session_unref(struct uvtd_session *sess)
508 {
509         if (!sess || !sess->ref || --sess->ref)
510                 return;
511
512         uvtd_session_unregister(sess);
513         free(sess);
514 }
515
516 void uvtd_session_unregister(struct uvtd_session *sess)
517 {
518         struct uvtd_seat *seat;
519         int ret;
520         bool forced = false;
521
522         if (!sess || !sess->seat)
523                 return;
524
525         log_debug("unregister session %p", sess);
526
527         seat = sess->seat;
528         sess->enabled = false;
529         if (seat->dummy_sess == sess)
530                 seat->dummy_sess = NULL;
531         seat_reschedule(seat);
532
533         if (seat->current_sess == sess) {
534                 ret = seat_pause(seat, true, SCHEDULE_NONE);
535                 if (ret) {
536                         forced = true;
537                         log_warning("unregistering active session %p; skipping automatic session-switch",
538                                     sess);
539                 }
540         }
541
542         shl_dlist_unlink(&sess->list);
543         --seat->session_count;
544         sess->seat = NULL;
545
546         session_call(sess, UVTD_SESSION_UNREGISTER);
547         uvtd_session_unref(sess);
548
549         /* If this session was active and we couldn't deactivate it, then it
550          * might still have resources allocated that couldn't get freed. In this
551          * case we should not automatically switch to the next session as it is
552          * very likely that it will not be able to start.
553          * Instead, we stay inactive and wait for user/external input to switch
554          * to another session. This delay will then hopefully be long enough so
555          * all resources got freed. */
556         if (!forced)
557                 seat_run(seat);
558 }
559
560 bool uvtd_session_is_registered(struct uvtd_session *sess)
561 {
562         return sess && sess->seat;
563 }
564
565 bool uvtd_session_is_active(struct uvtd_session *sess)
566 {
567         return sess && sess->seat && sess->seat->current_sess == sess;
568 }
569
570 void uvtd_session_schedule(struct uvtd_session *sess)
571 {
572         if (!sess || !sess->seat)
573                 return;
574
575         seat_schedule(sess->seat, sess);
576 }
577
578 void uvtd_session_enable(struct uvtd_session *sess)
579 {
580         if (!sess || sess->enabled)
581                 return;
582
583         log_debug("enable session %p", sess);
584         sess->enabled = true;
585
586         if (sess->seat &&
587             (!sess->seat->current_sess ||
588              sess->seat->current_sess == sess->seat->dummy_sess))
589                 seat_schedule(sess->seat, sess);
590 }
591
592 void uvtd_session_disable(struct uvtd_session *sess)
593 {
594         if (!sess || !sess->enabled)
595                 return;
596
597         log_debug("disable session %p", sess);
598         sess->enabled = false;
599 }
600
601 bool uvtd_session_is_enabled(struct uvtd_session *sess)
602 {
603         return sess && sess->enabled;
604 }
605
606 void uvtd_session_notify_deactivated(struct uvtd_session *sess)
607 {
608         struct uvtd_seat *seat;
609         unsigned int sched;
610
611         if (!sess || !sess->seat)
612                 return;
613
614         seat = sess->seat;
615         if (seat->current_sess != sess)
616                 return;
617
618         sched = seat->async_schedule;
619         log_debug("session %p notified core about deactivation (schedule: %u)",
620                   sess, sched);
621         seat_yield(seat);
622         seat_reschedule(seat);
623
624         if (sched == SCHEDULE_SLEEP)
625                 seat_go_asleep(seat, false);
626         else if (sched == SCHEDULE_UNREGISTER)
627                 uvtd_session_unregister(sess);
628         else
629                 seat_run(seat);
630 }