| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Functions for saving/restoring console. |
| * |
| * Originally from swsusp. |
| */ |
| |
| #include <linux/console.h> |
| #include <linux/vt_kern.h> |
| #include <linux/kbd_kern.h> |
| #include <linux/vt.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include "power.h" |
| |
| #define SUSPEND_CONSOLE (MAX_NR_CONSOLES-1) |
| |
| static int orig_fgconsole, orig_kmsg; |
| |
| static DEFINE_MUTEX(vt_switch_mutex); |
| |
| struct pm_vt_switch { |
| struct list_head head; |
| struct device *dev; |
| bool required; |
| }; |
| |
| static LIST_HEAD(pm_vt_switch_list); |
| |
| |
| /** |
| * pm_vt_switch_required - indicate VT switch at suspend requirements |
| * @dev: device |
| * @required: if true, caller needs VT switch at suspend/resume time |
| * |
| * The different console drivers may or may not require VT switches across |
| * suspend/resume, depending on how they handle restoring video state and |
| * what may be running. |
| * |
| * Drivers can indicate support for switchless suspend/resume, which can |
| * save time and flicker, by using this routine and passing 'false' as |
| * the argument. If any loaded driver needs VT switching, or the |
| * no_console_suspend argument has been passed on the command line, VT |
| * switches will occur. |
| */ |
| void pm_vt_switch_required(struct device *dev, bool required) |
| { |
| struct pm_vt_switch *entry, *tmp; |
| |
| mutex_lock(&vt_switch_mutex); |
| list_for_each_entry(tmp, &pm_vt_switch_list, head) { |
| if (tmp->dev == dev) { |
| /* already registered, update requirement */ |
| tmp->required = required; |
| goto out; |
| } |
| } |
| |
| entry = kmalloc(sizeof(*entry), GFP_KERNEL); |
| if (!entry) |
| goto out; |
| |
| entry->required = required; |
| entry->dev = dev; |
| |
| list_add(&entry->head, &pm_vt_switch_list); |
| out: |
| mutex_unlock(&vt_switch_mutex); |
| } |
| EXPORT_SYMBOL(pm_vt_switch_required); |
| |
| /** |
| * pm_vt_switch_unregister - stop tracking a device's VT switching needs |
| * @dev: device |
| * |
| * Remove @dev from the vt switch list. |
| */ |
| void pm_vt_switch_unregister(struct device *dev) |
| { |
| struct pm_vt_switch *tmp; |
| |
| mutex_lock(&vt_switch_mutex); |
| list_for_each_entry(tmp, &pm_vt_switch_list, head) { |
| if (tmp->dev == dev) { |
| list_del(&tmp->head); |
| kfree(tmp); |
| break; |
| } |
| } |
| mutex_unlock(&vt_switch_mutex); |
| } |
| EXPORT_SYMBOL(pm_vt_switch_unregister); |
| |
| /* |
| * There are three cases when a VT switch on suspend/resume are required: |
| * 1) no driver has indicated a requirement one way or another, so preserve |
| * the old behavior |
| * 2) console suspend is disabled, we want to see debug messages across |
| * suspend/resume |
| * 3) any registered driver indicates it needs a VT switch |
| * |
| * If none of these conditions is present, meaning we have at least one driver |
| * that doesn't need the switch, and none that do, we can avoid it to make |
| * resume look a little prettier (and suspend too, but that's usually hidden, |
| * e.g. when closing the lid on a laptop). |
| */ |
| static bool pm_vt_switch(void) |
| { |
| struct pm_vt_switch *entry; |
| bool ret = true; |
| |
| mutex_lock(&vt_switch_mutex); |
| if (list_empty(&pm_vt_switch_list)) |
| goto out; |
| |
| if (!console_suspend_enabled) |
| goto out; |
| |
| list_for_each_entry(entry, &pm_vt_switch_list, head) { |
| if (entry->required) |
| goto out; |
| } |
| |
| ret = false; |
| out: |
| mutex_unlock(&vt_switch_mutex); |
| return ret; |
| } |
| |
| void pm_prepare_console(void) |
| { |
| if (!pm_vt_switch()) |
| return; |
| |
| orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE, 1); |
| if (orig_fgconsole < 0) |
| return; |
| |
| orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE); |
| return; |
| } |
| |
| void pm_restore_console(void) |
| { |
| if (!pm_vt_switch()) |
| return; |
| |
| if (orig_fgconsole >= 0) { |
| vt_move_to_console(orig_fgconsole, 0); |
| vt_kmsg_redirect(orig_kmsg); |
| } |
| } |