GNU Linux-libre 4.19.211-gnu1
[releases.git] / kernel / power / console.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Functions for saving/restoring console.
4  *
5  * Originally from swsusp.
6  */
7
8 #include <linux/console.h>
9 #include <linux/vt_kern.h>
10 #include <linux/kbd_kern.h>
11 #include <linux/vt.h>
12 #include <linux/module.h>
13 #include <linux/slab.h>
14 #include "power.h"
15
16 #define SUSPEND_CONSOLE (MAX_NR_CONSOLES-1)
17
18 static int orig_fgconsole, orig_kmsg;
19
20 static DEFINE_MUTEX(vt_switch_mutex);
21
22 struct pm_vt_switch {
23         struct list_head head;
24         struct device *dev;
25         bool required;
26 };
27
28 static LIST_HEAD(pm_vt_switch_list);
29
30
31 /**
32  * pm_vt_switch_required - indicate VT switch at suspend requirements
33  * @dev: device
34  * @required: if true, caller needs VT switch at suspend/resume time
35  *
36  * The different console drivers may or may not require VT switches across
37  * suspend/resume, depending on how they handle restoring video state and
38  * what may be running.
39  *
40  * Drivers can indicate support for switchless suspend/resume, which can
41  * save time and flicker, by using this routine and passing 'false' as
42  * the argument.  If any loaded driver needs VT switching, or the
43  * no_console_suspend argument has been passed on the command line, VT
44  * switches will occur.
45  */
46 void pm_vt_switch_required(struct device *dev, bool required)
47 {
48         struct pm_vt_switch *entry, *tmp;
49
50         mutex_lock(&vt_switch_mutex);
51         list_for_each_entry(tmp, &pm_vt_switch_list, head) {
52                 if (tmp->dev == dev) {
53                         /* already registered, update requirement */
54                         tmp->required = required;
55                         goto out;
56                 }
57         }
58
59         entry = kmalloc(sizeof(*entry), GFP_KERNEL);
60         if (!entry)
61                 goto out;
62
63         entry->required = required;
64         entry->dev = dev;
65
66         list_add(&entry->head, &pm_vt_switch_list);
67 out:
68         mutex_unlock(&vt_switch_mutex);
69 }
70 EXPORT_SYMBOL(pm_vt_switch_required);
71
72 /**
73  * pm_vt_switch_unregister - stop tracking a device's VT switching needs
74  * @dev: device
75  *
76  * Remove @dev from the vt switch list.
77  */
78 void pm_vt_switch_unregister(struct device *dev)
79 {
80         struct pm_vt_switch *tmp;
81
82         mutex_lock(&vt_switch_mutex);
83         list_for_each_entry(tmp, &pm_vt_switch_list, head) {
84                 if (tmp->dev == dev) {
85                         list_del(&tmp->head);
86                         kfree(tmp);
87                         break;
88                 }
89         }
90         mutex_unlock(&vt_switch_mutex);
91 }
92 EXPORT_SYMBOL(pm_vt_switch_unregister);
93
94 /*
95  * There are three cases when a VT switch on suspend/resume are required:
96  *   1) no driver has indicated a requirement one way or another, so preserve
97  *      the old behavior
98  *   2) console suspend is disabled, we want to see debug messages across
99  *      suspend/resume
100  *   3) any registered driver indicates it needs a VT switch
101  *
102  * If none of these conditions is present, meaning we have at least one driver
103  * that doesn't need the switch, and none that do, we can avoid it to make
104  * resume look a little prettier (and suspend too, but that's usually hidden,
105  * e.g. when closing the lid on a laptop).
106  */
107 static bool pm_vt_switch(void)
108 {
109         struct pm_vt_switch *entry;
110         bool ret = true;
111
112         mutex_lock(&vt_switch_mutex);
113         if (list_empty(&pm_vt_switch_list))
114                 goto out;
115
116         if (!console_suspend_enabled)
117                 goto out;
118
119         list_for_each_entry(entry, &pm_vt_switch_list, head) {
120                 if (entry->required)
121                         goto out;
122         }
123
124         ret = false;
125 out:
126         mutex_unlock(&vt_switch_mutex);
127         return ret;
128 }
129
130 void pm_prepare_console(void)
131 {
132         if (!pm_vt_switch())
133                 return;
134
135         orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE, 1);
136         if (orig_fgconsole < 0)
137                 return;
138
139         orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
140         return;
141 }
142
143 void pm_restore_console(void)
144 {
145         if (!pm_vt_switch())
146                 return;
147
148         if (orig_fgconsole >= 0) {
149                 vt_move_to_console(orig_fgconsole, 0);
150                 vt_kmsg_redirect(orig_kmsg);
151         }
152 }