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