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  */
pm_vt_switch_required(struct device * dev,bool required)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  */
pm_vt_switch_unregister(struct device * dev)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  */
pm_vt_switch(void)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 
pm_prepare_console(void)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 
pm_restore_console(void)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 }
153