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(size: sizeof(*entry), GFP_KERNEL); |
60 | if (!entry) |
61 | goto out; |
62 | |
63 | entry->required = required; |
64 | entry->dev = dev; |
65 | |
66 | list_add(new: &entry->head, head: &pm_vt_switch_list); |
67 | out: |
68 | mutex_unlock(lock: &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(entry: &tmp->head); |
86 | kfree(objp: tmp); |
87 | break; |
88 | } |
89 | } |
90 | mutex_unlock(lock: &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(head: &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(lock: &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, alloc: 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(vt: orig_fgconsole, alloc: 0); |
150 | vt_kmsg_redirect(new: orig_kmsg); |
151 | } |
152 | } |
153 | |