mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +00:00
3ad11d7ac8
-----BEGIN PGP SIGNATURE----- iQJEBAABCAAuFiEEwPw5LcreJtl1+l5K99NY+ylx4KYFAl+EWUgQHGF4Ym9lQGtl cm5lbC5kawAKCRD301j7KXHgpnoxEADCVSNBRkpV0OVkOEC3wf8EGhXhk01Jnjtl u5Mg2V55hcgJ0thQxBV/V28XyqmsEBrmAVi0Yf8Vr9Qbq4Ze08Wae4ChS4rEOyh1 jTcGYWx5aJB3ChLvV/HI0nWQ3bkj03mMrL3SW8rhhf5DTyKHsVeTenpx42Qu/FKf fRzi09FSr3Pjd0B+EX6gunwJnlyXQC5Fa4AA0GhnXJzAznANXxHkkcXu8a6Yw75x e28CfhIBliORsK8sRHLoUnPpeTe1vtxCBhBMsE+gJAj9ZUOWMzvNFIPP4FvfawDy 6cCQo2m1azJ/IdZZCDjFUWyjh+wxdKMp+NNryEcoV+VlqIoc3n98rFwrSL+GIq5Z WVwEwq+AcwoMCsD29Lu1ytL2PQ/RVqcJP5UheMrbL4vzefNfJFumQVZLIcX0k943 8dFL2QHL+H/hM9Dx5y5rjeiWkAlq75v4xPKVjh/DHb4nehddCqn/+DD5HDhNANHf c1kmmEuYhvLpIaC4DHjE6DwLh8TPKahJjwsGuBOTr7D93NUQD+OOWsIhX6mNISIl FFhP8cd0/ZZVV//9j+q+5B4BaJsT+ZtwmrelKFnPdwPSnh+3iu8zPRRWO+8P8fRC YvddxuJAmE6BLmsAYrdz6Xb/wqfyV44cEiyivF0oBQfnhbtnXwDnkDWSfJD1bvCm ZwfpDh2+Tg== =LzyE -----END PGP SIGNATURE----- Merge tag 'block-5.10-2020-10-12' of git://git.kernel.dk/linux-block Pull block updates from Jens Axboe: - Series of merge handling cleanups (Baolin, Christoph) - Series of blk-throttle fixes and cleanups (Baolin) - Series cleaning up BDI, seperating the block device from the backing_dev_info (Christoph) - Removal of bdget() as a generic API (Christoph) - Removal of blkdev_get() as a generic API (Christoph) - Cleanup of is-partition checks (Christoph) - Series reworking disk revalidation (Christoph) - Series cleaning up bio flags (Christoph) - bio crypt fixes (Eric) - IO stats inflight tweak (Gabriel) - blk-mq tags fixes (Hannes) - Buffer invalidation fixes (Jan) - Allow soft limits for zone append (Johannes) - Shared tag set improvements (John, Kashyap) - Allow IOPRIO_CLASS_RT for CAP_SYS_NICE (Khazhismel) - DM no-wait support (Mike, Konstantin) - Request allocation improvements (Ming) - Allow md/dm/bcache to use IO stat helpers (Song) - Series improving blk-iocost (Tejun) - Various cleanups (Geert, Damien, Danny, Julia, Tetsuo, Tian, Wang, Xianting, Yang, Yufen, yangerkun) * tag 'block-5.10-2020-10-12' of git://git.kernel.dk/linux-block: (191 commits) block: fix uapi blkzoned.h comments blk-mq: move cancel of hctx->run_work to the front of blk_exit_queue blk-mq: get rid of the dead flush handle code path block: get rid of unnecessary local variable block: fix comment and add lockdep assert blk-mq: use helper function to test hw stopped block: use helper function to test queue register block: remove redundant mq check block: invoke blk_mq_exit_sched no matter whether have .exit_sched percpu_ref: don't refer to ref->data if it isn't allocated block: ratelimit handle_bad_sector() message blk-throttle: Re-use the throtl_set_slice_end() blk-throttle: Open code __throtl_de/enqueue_tg() blk-throttle: Move service tree validation out of the throtl_rb_first() blk-throttle: Move the list operation after list validation blk-throttle: Fix IO hang for a corner case blk-throttle: Avoid tracking latency if low limit is invalid blk-throttle: Avoid getting the current time if tg->last_finish_time is 0 blk-throttle: Remove a meaningless parameter for throtl_downgrade_state() block: Remove redundant 'return' statement ...
452 lines
9.4 KiB
C
452 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/kernel/power/user.c
|
|
*
|
|
* This file provides the user space interface for software suspend/resume.
|
|
*
|
|
* Copyright (C) 2006 Rafael J. Wysocki <rjw@sisk.pl>
|
|
*/
|
|
|
|
#include <linux/suspend.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/string.h>
|
|
#include <linux/device.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/swap.h>
|
|
#include <linux/swapops.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/console.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/freezer.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include "power.h"
|
|
|
|
|
|
static struct snapshot_data {
|
|
struct snapshot_handle handle;
|
|
int swap;
|
|
int mode;
|
|
bool frozen;
|
|
bool ready;
|
|
bool platform_support;
|
|
bool free_bitmaps;
|
|
dev_t dev;
|
|
} snapshot_state;
|
|
|
|
int is_hibernate_resume_dev(dev_t dev)
|
|
{
|
|
return hibernation_available() && snapshot_state.dev == dev;
|
|
}
|
|
|
|
static int snapshot_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct snapshot_data *data;
|
|
int error;
|
|
|
|
if (!hibernation_available())
|
|
return -EPERM;
|
|
|
|
lock_system_sleep();
|
|
|
|
if (!hibernate_acquire()) {
|
|
error = -EBUSY;
|
|
goto Unlock;
|
|
}
|
|
|
|
if ((filp->f_flags & O_ACCMODE) == O_RDWR) {
|
|
hibernate_release();
|
|
error = -ENOSYS;
|
|
goto Unlock;
|
|
}
|
|
nonseekable_open(inode, filp);
|
|
data = &snapshot_state;
|
|
filp->private_data = data;
|
|
memset(&data->handle, 0, sizeof(struct snapshot_handle));
|
|
if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
|
|
/* Hibernating. The image device should be accessible. */
|
|
data->swap = swap_type_of(swsusp_resume_device, 0);
|
|
data->mode = O_RDONLY;
|
|
data->free_bitmaps = false;
|
|
error = pm_notifier_call_chain_robust(PM_HIBERNATION_PREPARE, PM_POST_HIBERNATION);
|
|
} else {
|
|
/*
|
|
* Resuming. We may need to wait for the image device to
|
|
* appear.
|
|
*/
|
|
wait_for_device_probe();
|
|
|
|
data->swap = -1;
|
|
data->mode = O_WRONLY;
|
|
error = pm_notifier_call_chain_robust(PM_RESTORE_PREPARE, PM_POST_RESTORE);
|
|
if (!error) {
|
|
error = create_basic_memory_bitmaps();
|
|
data->free_bitmaps = !error;
|
|
}
|
|
}
|
|
if (error)
|
|
hibernate_release();
|
|
|
|
data->frozen = false;
|
|
data->ready = false;
|
|
data->platform_support = false;
|
|
data->dev = 0;
|
|
|
|
Unlock:
|
|
unlock_system_sleep();
|
|
|
|
return error;
|
|
}
|
|
|
|
static int snapshot_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct snapshot_data *data;
|
|
|
|
lock_system_sleep();
|
|
|
|
swsusp_free();
|
|
data = filp->private_data;
|
|
data->dev = 0;
|
|
free_all_swap_pages(data->swap);
|
|
if (data->frozen) {
|
|
pm_restore_gfp_mask();
|
|
free_basic_memory_bitmaps();
|
|
thaw_processes();
|
|
} else if (data->free_bitmaps) {
|
|
free_basic_memory_bitmaps();
|
|
}
|
|
pm_notifier_call_chain(data->mode == O_RDONLY ?
|
|
PM_POST_HIBERNATION : PM_POST_RESTORE);
|
|
hibernate_release();
|
|
|
|
unlock_system_sleep();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t snapshot_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *offp)
|
|
{
|
|
struct snapshot_data *data;
|
|
ssize_t res;
|
|
loff_t pg_offp = *offp & ~PAGE_MASK;
|
|
|
|
lock_system_sleep();
|
|
|
|
data = filp->private_data;
|
|
if (!data->ready) {
|
|
res = -ENODATA;
|
|
goto Unlock;
|
|
}
|
|
if (!pg_offp) { /* on page boundary? */
|
|
res = snapshot_read_next(&data->handle);
|
|
if (res <= 0)
|
|
goto Unlock;
|
|
} else {
|
|
res = PAGE_SIZE - pg_offp;
|
|
}
|
|
|
|
res = simple_read_from_buffer(buf, count, &pg_offp,
|
|
data_of(data->handle), res);
|
|
if (res > 0)
|
|
*offp += res;
|
|
|
|
Unlock:
|
|
unlock_system_sleep();
|
|
|
|
return res;
|
|
}
|
|
|
|
static ssize_t snapshot_write(struct file *filp, const char __user *buf,
|
|
size_t count, loff_t *offp)
|
|
{
|
|
struct snapshot_data *data;
|
|
ssize_t res;
|
|
loff_t pg_offp = *offp & ~PAGE_MASK;
|
|
|
|
lock_system_sleep();
|
|
|
|
data = filp->private_data;
|
|
|
|
if (!pg_offp) {
|
|
res = snapshot_write_next(&data->handle);
|
|
if (res <= 0)
|
|
goto unlock;
|
|
} else {
|
|
res = PAGE_SIZE - pg_offp;
|
|
}
|
|
|
|
if (!data_of(data->handle)) {
|
|
res = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
res = simple_write_to_buffer(data_of(data->handle), res, &pg_offp,
|
|
buf, count);
|
|
if (res > 0)
|
|
*offp += res;
|
|
unlock:
|
|
unlock_system_sleep();
|
|
|
|
return res;
|
|
}
|
|
|
|
struct compat_resume_swap_area {
|
|
compat_loff_t offset;
|
|
u32 dev;
|
|
} __packed;
|
|
|
|
static int snapshot_set_swap_area(struct snapshot_data *data,
|
|
void __user *argp)
|
|
{
|
|
sector_t offset;
|
|
dev_t swdev;
|
|
|
|
if (swsusp_swap_in_use())
|
|
return -EPERM;
|
|
|
|
if (in_compat_syscall()) {
|
|
struct compat_resume_swap_area swap_area;
|
|
|
|
if (copy_from_user(&swap_area, argp, sizeof(swap_area)))
|
|
return -EFAULT;
|
|
swdev = new_decode_dev(swap_area.dev);
|
|
offset = swap_area.offset;
|
|
} else {
|
|
struct resume_swap_area swap_area;
|
|
|
|
if (copy_from_user(&swap_area, argp, sizeof(swap_area)))
|
|
return -EFAULT;
|
|
swdev = new_decode_dev(swap_area.dev);
|
|
offset = swap_area.offset;
|
|
}
|
|
|
|
/*
|
|
* User space encodes device types as two-byte values,
|
|
* so we need to recode them
|
|
*/
|
|
data->swap = swap_type_of(swdev, offset);
|
|
if (data->swap < 0)
|
|
return swdev ? -ENODEV : -EINVAL;
|
|
data->dev = swdev;
|
|
return 0;
|
|
}
|
|
|
|
static long snapshot_ioctl(struct file *filp, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int error = 0;
|
|
struct snapshot_data *data;
|
|
loff_t size;
|
|
sector_t offset;
|
|
|
|
if (_IOC_TYPE(cmd) != SNAPSHOT_IOC_MAGIC)
|
|
return -ENOTTY;
|
|
if (_IOC_NR(cmd) > SNAPSHOT_IOC_MAXNR)
|
|
return -ENOTTY;
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (!mutex_trylock(&system_transition_mutex))
|
|
return -EBUSY;
|
|
|
|
lock_device_hotplug();
|
|
data = filp->private_data;
|
|
|
|
switch (cmd) {
|
|
|
|
case SNAPSHOT_FREEZE:
|
|
if (data->frozen)
|
|
break;
|
|
|
|
ksys_sync_helper();
|
|
|
|
error = freeze_processes();
|
|
if (error)
|
|
break;
|
|
|
|
error = create_basic_memory_bitmaps();
|
|
if (error)
|
|
thaw_processes();
|
|
else
|
|
data->frozen = true;
|
|
|
|
break;
|
|
|
|
case SNAPSHOT_UNFREEZE:
|
|
if (!data->frozen || data->ready)
|
|
break;
|
|
pm_restore_gfp_mask();
|
|
free_basic_memory_bitmaps();
|
|
data->free_bitmaps = false;
|
|
thaw_processes();
|
|
data->frozen = false;
|
|
break;
|
|
|
|
case SNAPSHOT_CREATE_IMAGE:
|
|
if (data->mode != O_RDONLY || !data->frozen || data->ready) {
|
|
error = -EPERM;
|
|
break;
|
|
}
|
|
pm_restore_gfp_mask();
|
|
error = hibernation_snapshot(data->platform_support);
|
|
if (!error) {
|
|
error = put_user(in_suspend, (int __user *)arg);
|
|
data->ready = !freezer_test_done && !error;
|
|
freezer_test_done = false;
|
|
}
|
|
break;
|
|
|
|
case SNAPSHOT_ATOMIC_RESTORE:
|
|
snapshot_write_finalize(&data->handle);
|
|
if (data->mode != O_WRONLY || !data->frozen ||
|
|
!snapshot_image_loaded(&data->handle)) {
|
|
error = -EPERM;
|
|
break;
|
|
}
|
|
error = hibernation_restore(data->platform_support);
|
|
break;
|
|
|
|
case SNAPSHOT_FREE:
|
|
swsusp_free();
|
|
memset(&data->handle, 0, sizeof(struct snapshot_handle));
|
|
data->ready = false;
|
|
/*
|
|
* It is necessary to thaw kernel threads here, because
|
|
* SNAPSHOT_CREATE_IMAGE may be invoked directly after
|
|
* SNAPSHOT_FREE. In that case, if kernel threads were not
|
|
* thawed, the preallocation of memory carried out by
|
|
* hibernation_snapshot() might run into problems (i.e. it
|
|
* might fail or even deadlock).
|
|
*/
|
|
thaw_kernel_threads();
|
|
break;
|
|
|
|
case SNAPSHOT_PREF_IMAGE_SIZE:
|
|
image_size = arg;
|
|
break;
|
|
|
|
case SNAPSHOT_GET_IMAGE_SIZE:
|
|
if (!data->ready) {
|
|
error = -ENODATA;
|
|
break;
|
|
}
|
|
size = snapshot_get_image_size();
|
|
size <<= PAGE_SHIFT;
|
|
error = put_user(size, (loff_t __user *)arg);
|
|
break;
|
|
|
|
case SNAPSHOT_AVAIL_SWAP_SIZE:
|
|
size = count_swap_pages(data->swap, 1);
|
|
size <<= PAGE_SHIFT;
|
|
error = put_user(size, (loff_t __user *)arg);
|
|
break;
|
|
|
|
case SNAPSHOT_ALLOC_SWAP_PAGE:
|
|
if (data->swap < 0 || data->swap >= MAX_SWAPFILES) {
|
|
error = -ENODEV;
|
|
break;
|
|
}
|
|
offset = alloc_swapdev_block(data->swap);
|
|
if (offset) {
|
|
offset <<= PAGE_SHIFT;
|
|
error = put_user(offset, (loff_t __user *)arg);
|
|
} else {
|
|
error = -ENOSPC;
|
|
}
|
|
break;
|
|
|
|
case SNAPSHOT_FREE_SWAP_PAGES:
|
|
if (data->swap < 0 || data->swap >= MAX_SWAPFILES) {
|
|
error = -ENODEV;
|
|
break;
|
|
}
|
|
free_all_swap_pages(data->swap);
|
|
break;
|
|
|
|
case SNAPSHOT_S2RAM:
|
|
if (!data->frozen) {
|
|
error = -EPERM;
|
|
break;
|
|
}
|
|
/*
|
|
* Tasks are frozen and the notifiers have been called with
|
|
* PM_HIBERNATION_PREPARE
|
|
*/
|
|
error = suspend_devices_and_enter(PM_SUSPEND_MEM);
|
|
data->ready = false;
|
|
break;
|
|
|
|
case SNAPSHOT_PLATFORM_SUPPORT:
|
|
data->platform_support = !!arg;
|
|
break;
|
|
|
|
case SNAPSHOT_POWER_OFF:
|
|
if (data->platform_support)
|
|
error = hibernation_platform_enter();
|
|
break;
|
|
|
|
case SNAPSHOT_SET_SWAP_AREA:
|
|
error = snapshot_set_swap_area(data, (void __user *)arg);
|
|
break;
|
|
|
|
default:
|
|
error = -ENOTTY;
|
|
|
|
}
|
|
|
|
unlock_device_hotplug();
|
|
mutex_unlock(&system_transition_mutex);
|
|
|
|
return error;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static long
|
|
snapshot_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
BUILD_BUG_ON(sizeof(loff_t) != sizeof(compat_loff_t));
|
|
|
|
switch (cmd) {
|
|
case SNAPSHOT_GET_IMAGE_SIZE:
|
|
case SNAPSHOT_AVAIL_SWAP_SIZE:
|
|
case SNAPSHOT_ALLOC_SWAP_PAGE:
|
|
case SNAPSHOT_CREATE_IMAGE:
|
|
case SNAPSHOT_SET_SWAP_AREA:
|
|
return snapshot_ioctl(file, cmd,
|
|
(unsigned long) compat_ptr(arg));
|
|
default:
|
|
return snapshot_ioctl(file, cmd, arg);
|
|
}
|
|
}
|
|
#endif /* CONFIG_COMPAT */
|
|
|
|
static const struct file_operations snapshot_fops = {
|
|
.open = snapshot_open,
|
|
.release = snapshot_release,
|
|
.read = snapshot_read,
|
|
.write = snapshot_write,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = snapshot_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = snapshot_compat_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static struct miscdevice snapshot_device = {
|
|
.minor = SNAPSHOT_MINOR,
|
|
.name = "snapshot",
|
|
.fops = &snapshot_fops,
|
|
};
|
|
|
|
static int __init snapshot_device_init(void)
|
|
{
|
|
return misc_register(&snapshot_device);
|
|
};
|
|
|
|
device_initcall(snapshot_device_init);
|