xref: /openbmc/linux/kernel/power/user.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
155716d26SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26e1819d6SRafael J. Wysocki /*
36e1819d6SRafael J. Wysocki  * linux/kernel/power/user.c
46e1819d6SRafael J. Wysocki  *
56e1819d6SRafael J. Wysocki  * This file provides the user space interface for software suspend/resume.
66e1819d6SRafael J. Wysocki  *
76e1819d6SRafael J. Wysocki  * Copyright (C) 2006 Rafael J. Wysocki <rjw@sisk.pl>
86e1819d6SRafael J. Wysocki  */
96e1819d6SRafael J. Wysocki 
106e1819d6SRafael J. Wysocki #include <linux/suspend.h>
113592695cSStefan Seyfried #include <linux/reboot.h>
126e1819d6SRafael J. Wysocki #include <linux/string.h>
136e1819d6SRafael J. Wysocki #include <linux/device.h>
146e1819d6SRafael J. Wysocki #include <linux/miscdevice.h>
156e1819d6SRafael J. Wysocki #include <linux/mm.h>
166e1819d6SRafael J. Wysocki #include <linux/swap.h>
176e1819d6SRafael J. Wysocki #include <linux/swapops.h>
186e1819d6SRafael J. Wysocki #include <linux/pm.h>
196e1819d6SRafael J. Wysocki #include <linux/fs.h>
20c336078bSBen Hutchings #include <linux/compat.h>
2197c7801cSRafael J. Wysocki #include <linux/console.h>
22e3920fb4SRafael J. Wysocki #include <linux/cpu.h>
237dfb7103SNigel Cunningham #include <linux/freezer.h>
246e1819d6SRafael J. Wysocki 
257c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
266e1819d6SRafael J. Wysocki 
276e1819d6SRafael J. Wysocki #include "power.h"
286e1819d6SRafael J. Wysocki 
298386c414STetsuo Handa static bool need_wait;
30eb57c1cfSRafael J. Wysocki 
316e1819d6SRafael J. Wysocki static struct snapshot_data {
326e1819d6SRafael J. Wysocki 	struct snapshot_handle handle;
336e1819d6SRafael J. Wysocki 	int swap;
346e1819d6SRafael J. Wysocki 	int mode;
357bc9b1cfSRafael J. Wysocki 	bool frozen;
367bc9b1cfSRafael J. Wysocki 	bool ready;
377bc9b1cfSRafael J. Wysocki 	bool platform_support;
38aab17289SRafael J. Wysocki 	bool free_bitmaps;
39bb3247a3SChristoph Hellwig 	dev_t dev;
406e1819d6SRafael J. Wysocki } snapshot_state;
416e1819d6SRafael J. Wysocki 
is_hibernate_resume_dev(dev_t dev)42bb3247a3SChristoph Hellwig int is_hibernate_resume_dev(dev_t dev)
43ad1e4f74SDomenico Andreoli {
44bb3247a3SChristoph Hellwig 	return hibernation_available() && snapshot_state.dev == dev;
45ad1e4f74SDomenico Andreoli }
46ad1e4f74SDomenico Andreoli 
snapshot_open(struct inode * inode,struct file * filp)476e1819d6SRafael J. Wysocki static int snapshot_open(struct inode *inode, struct file *filp)
486e1819d6SRafael J. Wysocki {
496e1819d6SRafael J. Wysocki 	struct snapshot_data *data;
50*5950e5d5SPeter Zijlstra 	unsigned int sleep_flags;
5170d93298SPeter Zijlstra 	int error;
526e1819d6SRafael J. Wysocki 
53a6e15a39SKees Cook 	if (!hibernation_available())
54a6e15a39SKees Cook 		return -EPERM;
55a6e15a39SKees Cook 
56*5950e5d5SPeter Zijlstra 	sleep_flags = lock_system_sleep();
5725f2f3daSRafael J. Wysocki 
58ab7e9b06SDomenico Andreoli 	if (!hibernate_acquire()) {
5925f2f3daSRafael J. Wysocki 		error = -EBUSY;
6025f2f3daSRafael J. Wysocki 		goto Unlock;
6125f2f3daSRafael J. Wysocki 	}
626e1819d6SRafael J. Wysocki 
631525a2adSRafael J. Wysocki 	if ((filp->f_flags & O_ACCMODE) == O_RDWR) {
64ab7e9b06SDomenico Andreoli 		hibernate_release();
6525f2f3daSRafael J. Wysocki 		error = -ENOSYS;
6625f2f3daSRafael J. Wysocki 		goto Unlock;
671525a2adSRafael J. Wysocki 	}
686e1819d6SRafael J. Wysocki 	nonseekable_open(inode, filp);
696e1819d6SRafael J. Wysocki 	data = &snapshot_state;
706e1819d6SRafael J. Wysocki 	filp->private_data = data;
716e1819d6SRafael J. Wysocki 	memset(&data->handle, 0, sizeof(struct snapshot_handle));
726e1819d6SRafael J. Wysocki 	if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
73c7510859SRafael J. Wysocki 		/* Hibernating.  The image device should be accessible. */
7421bd9005SChristoph Hellwig 		data->swap = swap_type_of(swsusp_resume_device, 0);
756e1819d6SRafael J. Wysocki 		data->mode = O_RDONLY;
766a0c7cd3SRafael J. Wysocki 		data->free_bitmaps = false;
7770d93298SPeter Zijlstra 		error = pm_notifier_call_chain_robust(PM_HIBERNATION_PREPARE, PM_POST_HIBERNATION);
78ebae2604SAndrey Borzenkov 	} else {
79c7510859SRafael J. Wysocki 		/*
80c7510859SRafael J. Wysocki 		 * Resuming.  We may need to wait for the image device to
81c7510859SRafael J. Wysocki 		 * appear.
82c7510859SRafael J. Wysocki 		 */
838386c414STetsuo Handa 		need_wait = true;
84c7510859SRafael J. Wysocki 
85ebae2604SAndrey Borzenkov 		data->swap = -1;
86ebae2604SAndrey Borzenkov 		data->mode = O_WRONLY;
8770d93298SPeter Zijlstra 		error = pm_notifier_call_chain_robust(PM_RESTORE_PREPARE, PM_POST_RESTORE);
88aab17289SRafael J. Wysocki 		if (!error) {
89aab17289SRafael J. Wysocki 			error = create_basic_memory_bitmaps();
90aab17289SRafael J. Wysocki 			data->free_bitmaps = !error;
9170d93298SPeter Zijlstra 		}
92c3e94d89SAlan Stern 	}
938fd37a4cSRafael J. Wysocki 	if (error)
94ab7e9b06SDomenico Andreoli 		hibernate_release();
958fd37a4cSRafael J. Wysocki 
967bc9b1cfSRafael J. Wysocki 	data->frozen = false;
977bc9b1cfSRafael J. Wysocki 	data->ready = false;
987bc9b1cfSRafael J. Wysocki 	data->platform_support = false;
99bb3247a3SChristoph Hellwig 	data->dev = 0;
1006e1819d6SRafael J. Wysocki 
10125f2f3daSRafael J. Wysocki  Unlock:
102*5950e5d5SPeter Zijlstra 	unlock_system_sleep(sleep_flags);
10325f2f3daSRafael J. Wysocki 
10425f2f3daSRafael J. Wysocki 	return error;
1056e1819d6SRafael J. Wysocki }
1066e1819d6SRafael J. Wysocki 
snapshot_release(struct inode * inode,struct file * filp)1076e1819d6SRafael J. Wysocki static int snapshot_release(struct inode *inode, struct file *filp)
1086e1819d6SRafael J. Wysocki {
1096e1819d6SRafael J. Wysocki 	struct snapshot_data *data;
110*5950e5d5SPeter Zijlstra 	unsigned int sleep_flags;
1116e1819d6SRafael J. Wysocki 
112*5950e5d5SPeter Zijlstra 	sleep_flags = lock_system_sleep();
11325f2f3daSRafael J. Wysocki 
1146e1819d6SRafael J. Wysocki 	swsusp_free();
1156e1819d6SRafael J. Wysocki 	data = filp->private_data;
116bb3247a3SChristoph Hellwig 	data->dev = 0;
117d1d241ccSRafael J. Wysocki 	free_all_swap_pages(data->swap);
1189744997aSRafael J. Wysocki 	if (data->frozen) {
1199744997aSRafael J. Wysocki 		pm_restore_gfp_mask();
1208fd37a4cSRafael J. Wysocki 		free_basic_memory_bitmaps();
1216e1819d6SRafael J. Wysocki 		thaw_processes();
122aab17289SRafael J. Wysocki 	} else if (data->free_bitmaps) {
123aab17289SRafael J. Wysocki 		free_basic_memory_bitmaps();
1249744997aSRafael J. Wysocki 	}
1251497dd1dSTakashi Iwai 	pm_notifier_call_chain(data->mode == O_RDONLY ?
126c3e94d89SAlan Stern 			PM_POST_HIBERNATION : PM_POST_RESTORE);
127ab7e9b06SDomenico Andreoli 	hibernate_release();
12825f2f3daSRafael J. Wysocki 
129*5950e5d5SPeter Zijlstra 	unlock_system_sleep(sleep_flags);
13025f2f3daSRafael J. Wysocki 
1316e1819d6SRafael J. Wysocki 	return 0;
1326e1819d6SRafael J. Wysocki }
1336e1819d6SRafael J. Wysocki 
snapshot_read(struct file * filp,char __user * buf,size_t count,loff_t * offp)1346e1819d6SRafael J. Wysocki static ssize_t snapshot_read(struct file *filp, char __user *buf,
1356e1819d6SRafael J. Wysocki                              size_t count, loff_t *offp)
1366e1819d6SRafael J. Wysocki {
137d3c1b24cSJiri Slaby 	loff_t pg_offp = *offp & ~PAGE_MASK;
138*5950e5d5SPeter Zijlstra 	struct snapshot_data *data;
139*5950e5d5SPeter Zijlstra 	unsigned int sleep_flags;
140*5950e5d5SPeter Zijlstra 	ssize_t res;
1416e1819d6SRafael J. Wysocki 
142*5950e5d5SPeter Zijlstra 	sleep_flags = lock_system_sleep();
14325f2f3daSRafael J. Wysocki 
1446e1819d6SRafael J. Wysocki 	data = filp->private_data;
14525f2f3daSRafael J. Wysocki 	if (!data->ready) {
14625f2f3daSRafael J. Wysocki 		res = -ENODATA;
14725f2f3daSRafael J. Wysocki 		goto Unlock;
14825f2f3daSRafael J. Wysocki 	}
149d3c1b24cSJiri Slaby 	if (!pg_offp) { /* on page boundary? */
150d3c1b24cSJiri Slaby 		res = snapshot_read_next(&data->handle);
151d3c1b24cSJiri Slaby 		if (res <= 0)
152d3c1b24cSJiri Slaby 			goto Unlock;
153d3c1b24cSJiri Slaby 	} else {
154d3c1b24cSJiri Slaby 		res = PAGE_SIZE - pg_offp;
1556e1819d6SRafael J. Wysocki 	}
15625f2f3daSRafael J. Wysocki 
157d3c1b24cSJiri Slaby 	res = simple_read_from_buffer(buf, count, &pg_offp,
158d3c1b24cSJiri Slaby 			data_of(data->handle), res);
159d3c1b24cSJiri Slaby 	if (res > 0)
160d3c1b24cSJiri Slaby 		*offp += res;
161d3c1b24cSJiri Slaby 
16225f2f3daSRafael J. Wysocki  Unlock:
163*5950e5d5SPeter Zijlstra 	unlock_system_sleep(sleep_flags);
16425f2f3daSRafael J. Wysocki 
1656e1819d6SRafael J. Wysocki 	return res;
1666e1819d6SRafael J. Wysocki }
1676e1819d6SRafael J. Wysocki 
snapshot_write(struct file * filp,const char __user * buf,size_t count,loff_t * offp)1686e1819d6SRafael J. Wysocki static ssize_t snapshot_write(struct file *filp, const char __user *buf,
1696e1819d6SRafael J. Wysocki                               size_t count, loff_t *offp)
1706e1819d6SRafael J. Wysocki {
171d3c1b24cSJiri Slaby 	loff_t pg_offp = *offp & ~PAGE_MASK;
172*5950e5d5SPeter Zijlstra 	struct snapshot_data *data;
173*5950e5d5SPeter Zijlstra 	unsigned long sleep_flags;
174*5950e5d5SPeter Zijlstra 	ssize_t res;
1756e1819d6SRafael J. Wysocki 
1768386c414STetsuo Handa 	if (need_wait) {
1778386c414STetsuo Handa 		wait_for_device_probe();
1788386c414STetsuo Handa 		need_wait = false;
1798386c414STetsuo Handa 	}
1808386c414STetsuo Handa 
181*5950e5d5SPeter Zijlstra 	sleep_flags = lock_system_sleep();
18225f2f3daSRafael J. Wysocki 
1836e1819d6SRafael J. Wysocki 	data = filp->private_data;
184d3c1b24cSJiri Slaby 
185d3c1b24cSJiri Slaby 	if (!pg_offp) {
186d3c1b24cSJiri Slaby 		res = snapshot_write_next(&data->handle);
187d3c1b24cSJiri Slaby 		if (res <= 0)
188d3c1b24cSJiri Slaby 			goto unlock;
189d3c1b24cSJiri Slaby 	} else {
19088a5045fSEvan Green 		res = PAGE_SIZE;
1916e1819d6SRafael J. Wysocki 	}
19225f2f3daSRafael J. Wysocki 
193fc14eebfSTetsuo Handa 	if (!data_of(data->handle)) {
194fc14eebfSTetsuo Handa 		res = -EINVAL;
195fc14eebfSTetsuo Handa 		goto unlock;
196fc14eebfSTetsuo Handa 	}
197fc14eebfSTetsuo Handa 
198d3c1b24cSJiri Slaby 	res = simple_write_to_buffer(data_of(data->handle), res, &pg_offp,
199d3c1b24cSJiri Slaby 			buf, count);
200d3c1b24cSJiri Slaby 	if (res > 0)
201d3c1b24cSJiri Slaby 		*offp += res;
202d3c1b24cSJiri Slaby unlock:
203*5950e5d5SPeter Zijlstra 	unlock_system_sleep(sleep_flags);
20425f2f3daSRafael J. Wysocki 
2056e1819d6SRafael J. Wysocki 	return res;
2066e1819d6SRafael J. Wysocki }
2076e1819d6SRafael J. Wysocki 
2080f5c4c6eSChristoph Hellwig struct compat_resume_swap_area {
2090f5c4c6eSChristoph Hellwig 	compat_loff_t offset;
2100f5c4c6eSChristoph Hellwig 	u32 dev;
2110f5c4c6eSChristoph Hellwig } __packed;
2120f5c4c6eSChristoph Hellwig 
snapshot_set_swap_area(struct snapshot_data * data,void __user * argp)21388a77559SChristoph Hellwig static int snapshot_set_swap_area(struct snapshot_data *data,
21488a77559SChristoph Hellwig 		void __user *argp)
21588a77559SChristoph Hellwig {
21688a77559SChristoph Hellwig 	sector_t offset;
21788a77559SChristoph Hellwig 	dev_t swdev;
21888a77559SChristoph Hellwig 
21988a77559SChristoph Hellwig 	if (swsusp_swap_in_use())
22088a77559SChristoph Hellwig 		return -EPERM;
2210f5c4c6eSChristoph Hellwig 
2220f5c4c6eSChristoph Hellwig 	if (in_compat_syscall()) {
2230f5c4c6eSChristoph Hellwig 		struct compat_resume_swap_area swap_area;
2240f5c4c6eSChristoph Hellwig 
22588a77559SChristoph Hellwig 		if (copy_from_user(&swap_area, argp, sizeof(swap_area)))
22688a77559SChristoph Hellwig 			return -EFAULT;
2270f5c4c6eSChristoph Hellwig 		swdev = new_decode_dev(swap_area.dev);
2280f5c4c6eSChristoph Hellwig 		offset = swap_area.offset;
2290f5c4c6eSChristoph Hellwig 	} else {
2300f5c4c6eSChristoph Hellwig 		struct resume_swap_area swap_area;
2310f5c4c6eSChristoph Hellwig 
2320f5c4c6eSChristoph Hellwig 		if (copy_from_user(&swap_area, argp, sizeof(swap_area)))
2330f5c4c6eSChristoph Hellwig 			return -EFAULT;
2340f5c4c6eSChristoph Hellwig 		swdev = new_decode_dev(swap_area.dev);
2350f5c4c6eSChristoph Hellwig 		offset = swap_area.offset;
2360f5c4c6eSChristoph Hellwig 	}
23788a77559SChristoph Hellwig 
23888a77559SChristoph Hellwig 	/*
23988a77559SChristoph Hellwig 	 * User space encodes device types as two-byte values,
24088a77559SChristoph Hellwig 	 * so we need to recode them
24188a77559SChristoph Hellwig 	 */
24221bd9005SChristoph Hellwig 	data->swap = swap_type_of(swdev, offset);
24388a77559SChristoph Hellwig 	if (data->swap < 0)
24421bd9005SChristoph Hellwig 		return swdev ? -ENODEV : -EINVAL;
24521bd9005SChristoph Hellwig 	data->dev = swdev;
24688a77559SChristoph Hellwig 	return 0;
24788a77559SChristoph Hellwig }
24888a77559SChristoph Hellwig 
snapshot_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)24952d11025SAlan Cox static long snapshot_ioctl(struct file *filp, unsigned int cmd,
25052d11025SAlan Cox 							unsigned long arg)
2516e1819d6SRafael J. Wysocki {
2526e1819d6SRafael J. Wysocki 	int error = 0;
2536e1819d6SRafael J. Wysocki 	struct snapshot_data *data;
254af508b34SRafael J. Wysocki 	loff_t size;
2553aef83e0SRafael J. Wysocki 	sector_t offset;
2566e1819d6SRafael J. Wysocki 
2578386c414STetsuo Handa 	if (need_wait) {
2588386c414STetsuo Handa 		wait_for_device_probe();
2598386c414STetsuo Handa 		need_wait = false;
2608386c414STetsuo Handa 	}
2618386c414STetsuo Handa 
2626e1819d6SRafael J. Wysocki 	if (_IOC_TYPE(cmd) != SNAPSHOT_IOC_MAGIC)
2636e1819d6SRafael J. Wysocki 		return -ENOTTY;
2646e1819d6SRafael J. Wysocki 	if (_IOC_NR(cmd) > SNAPSHOT_IOC_MAXNR)
2656e1819d6SRafael J. Wysocki 		return -ENOTTY;
2666e1819d6SRafael J. Wysocki 	if (!capable(CAP_SYS_ADMIN))
2676e1819d6SRafael J. Wysocki 		return -EPERM;
2686e1819d6SRafael J. Wysocki 
26955f2503cSPingfan Liu 	if (!mutex_trylock(&system_transition_mutex))
27025f2f3daSRafael J. Wysocki 		return -EBUSY;
2716e1819d6SRafael J. Wysocki 
272942f4015SRafael J. Wysocki 	lock_device_hotplug();
27325f2f3daSRafael J. Wysocki 	data = filp->private_data;
27452d11025SAlan Cox 
2756e1819d6SRafael J. Wysocki 	switch (cmd) {
2766e1819d6SRafael J. Wysocki 
2776e1819d6SRafael J. Wysocki 	case SNAPSHOT_FREEZE:
2786e1819d6SRafael J. Wysocki 		if (data->frozen)
2796e1819d6SRafael J. Wysocki 			break;
2801bfcf130SRafael J. Wysocki 
281b5dee313SHarry Pan 		ksys_sync_helper();
282232b1432SRafael J. Wysocki 
2831bfcf130SRafael J. Wysocki 		error = freeze_processes();
2848fd37a4cSRafael J. Wysocki 		if (error)
2858fd37a4cSRafael J. Wysocki 			break;
2868fd37a4cSRafael J. Wysocki 
2878fd37a4cSRafael J. Wysocki 		error = create_basic_memory_bitmaps();
2888fd37a4cSRafael J. Wysocki 		if (error)
2898fd37a4cSRafael J. Wysocki 			thaw_processes();
2908fd37a4cSRafael J. Wysocki 		else
2917bc9b1cfSRafael J. Wysocki 			data->frozen = true;
2928fd37a4cSRafael J. Wysocki 
2936e1819d6SRafael J. Wysocki 		break;
2946e1819d6SRafael J. Wysocki 
2956e1819d6SRafael J. Wysocki 	case SNAPSHOT_UNFREEZE:
2962f41dddbSRafael J. Wysocki 		if (!data->frozen || data->ready)
2976e1819d6SRafael J. Wysocki 			break;
298c9e664f1SRafael J. Wysocki 		pm_restore_gfp_mask();
2998fd37a4cSRafael J. Wysocki 		free_basic_memory_bitmaps();
300aab17289SRafael J. Wysocki 		data->free_bitmaps = false;
3016e1819d6SRafael J. Wysocki 		thaw_processes();
3027bc9b1cfSRafael J. Wysocki 		data->frozen = false;
3036e1819d6SRafael J. Wysocki 		break;
3046e1819d6SRafael J. Wysocki 
305b694e52eSJiri Slaby 	case SNAPSHOT_CREATE_IMAGE:
3066e1819d6SRafael J. Wysocki 		if (data->mode != O_RDONLY || !data->frozen  || data->ready) {
3076e1819d6SRafael J. Wysocki 			error = -EPERM;
3086e1819d6SRafael J. Wysocki 			break;
3096e1819d6SRafael J. Wysocki 		}
310c9e664f1SRafael J. Wysocki 		pm_restore_gfp_mask();
311eb57c1cfSRafael J. Wysocki 		error = hibernation_snapshot(data->platform_support);
31251d6ff7aSSrivatsa S. Bhat 		if (!error) {
313cc5d207cSRafael J. Wysocki 			error = put_user(in_suspend, (int __user *)arg);
314a556d5b5SSrivatsa S. Bhat 			data->ready = !freezer_test_done && !error;
31597819a26SSrivatsa S. Bhat 			freezer_test_done = false;
31697819a26SSrivatsa S. Bhat 		}
3176e1819d6SRafael J. Wysocki 		break;
3186e1819d6SRafael J. Wysocki 
3196e1819d6SRafael J. Wysocki 	case SNAPSHOT_ATOMIC_RESTORE:
3208357376dSRafael J. Wysocki 		snapshot_write_finalize(&data->handle);
3216e1819d6SRafael J. Wysocki 		if (data->mode != O_WRONLY || !data->frozen ||
3226e1819d6SRafael J. Wysocki 		    !snapshot_image_loaded(&data->handle)) {
3236e1819d6SRafael J. Wysocki 			error = -EPERM;
3246e1819d6SRafael J. Wysocki 			break;
3256e1819d6SRafael J. Wysocki 		}
326eb57c1cfSRafael J. Wysocki 		error = hibernation_restore(data->platform_support);
3276e1819d6SRafael J. Wysocki 		break;
3286e1819d6SRafael J. Wysocki 
3296e1819d6SRafael J. Wysocki 	case SNAPSHOT_FREE:
3306e1819d6SRafael J. Wysocki 		swsusp_free();
3316e1819d6SRafael J. Wysocki 		memset(&data->handle, 0, sizeof(struct snapshot_handle));
3327bc9b1cfSRafael J. Wysocki 		data->ready = false;
333181e9bdeSRafael J. Wysocki 		/*
334181e9bdeSRafael J. Wysocki 		 * It is necessary to thaw kernel threads here, because
335181e9bdeSRafael J. Wysocki 		 * SNAPSHOT_CREATE_IMAGE may be invoked directly after
336181e9bdeSRafael J. Wysocki 		 * SNAPSHOT_FREE.  In that case, if kernel threads were not
337181e9bdeSRafael J. Wysocki 		 * thawed, the preallocation of memory carried out by
338181e9bdeSRafael J. Wysocki 		 * hibernation_snapshot() might run into problems (i.e. it
339181e9bdeSRafael J. Wysocki 		 * might fail or even deadlock).
340181e9bdeSRafael J. Wysocki 		 */
341181e9bdeSRafael J. Wysocki 		thaw_kernel_threads();
3426e1819d6SRafael J. Wysocki 		break;
3436e1819d6SRafael J. Wysocki 
344b694e52eSJiri Slaby 	case SNAPSHOT_PREF_IMAGE_SIZE:
3456e1819d6SRafael J. Wysocki 		image_size = arg;
3466e1819d6SRafael J. Wysocki 		break;
3476e1819d6SRafael J. Wysocki 
348af508b34SRafael J. Wysocki 	case SNAPSHOT_GET_IMAGE_SIZE:
349af508b34SRafael J. Wysocki 		if (!data->ready) {
350af508b34SRafael J. Wysocki 			error = -ENODATA;
351af508b34SRafael J. Wysocki 			break;
352af508b34SRafael J. Wysocki 		}
353af508b34SRafael J. Wysocki 		size = snapshot_get_image_size();
354af508b34SRafael J. Wysocki 		size <<= PAGE_SHIFT;
355af508b34SRafael J. Wysocki 		error = put_user(size, (loff_t __user *)arg);
356af508b34SRafael J. Wysocki 		break;
357af508b34SRafael J. Wysocki 
358b694e52eSJiri Slaby 	case SNAPSHOT_AVAIL_SWAP_SIZE:
359af508b34SRafael J. Wysocki 		size = count_swap_pages(data->swap, 1);
360af508b34SRafael J. Wysocki 		size <<= PAGE_SHIFT;
361af508b34SRafael J. Wysocki 		error = put_user(size, (loff_t __user *)arg);
3626e1819d6SRafael J. Wysocki 		break;
3636e1819d6SRafael J. Wysocki 
364b694e52eSJiri Slaby 	case SNAPSHOT_ALLOC_SWAP_PAGE:
3656e1819d6SRafael J. Wysocki 		if (data->swap < 0 || data->swap >= MAX_SWAPFILES) {
3666e1819d6SRafael J. Wysocki 			error = -ENODEV;
3676e1819d6SRafael J. Wysocki 			break;
3686e1819d6SRafael J. Wysocki 		}
369d1d241ccSRafael J. Wysocki 		offset = alloc_swapdev_block(data->swap);
3706e1819d6SRafael J. Wysocki 		if (offset) {
3716e1819d6SRafael J. Wysocki 			offset <<= PAGE_SHIFT;
372cc5d207cSRafael J. Wysocki 			error = put_user(offset, (loff_t __user *)arg);
3736e1819d6SRafael J. Wysocki 		} else {
3746e1819d6SRafael J. Wysocki 			error = -ENOSPC;
3756e1819d6SRafael J. Wysocki 		}
3766e1819d6SRafael J. Wysocki 		break;
3776e1819d6SRafael J. Wysocki 
3786e1819d6SRafael J. Wysocki 	case SNAPSHOT_FREE_SWAP_PAGES:
3796e1819d6SRafael J. Wysocki 		if (data->swap < 0 || data->swap >= MAX_SWAPFILES) {
3806e1819d6SRafael J. Wysocki 			error = -ENODEV;
3816e1819d6SRafael J. Wysocki 			break;
3826e1819d6SRafael J. Wysocki 		}
383d1d241ccSRafael J. Wysocki 		free_all_swap_pages(data->swap);
3846e1819d6SRafael J. Wysocki 		break;
3856e1819d6SRafael J. Wysocki 
3869b238205SLuca Tettamanti 	case SNAPSHOT_S2RAM:
3879b238205SLuca Tettamanti 		if (!data->frozen) {
3889b238205SLuca Tettamanti 			error = -EPERM;
3899b238205SLuca Tettamanti 			break;
3909b238205SLuca Tettamanti 		}
3916c961dfbSRafael J. Wysocki 		/*
3926c961dfbSRafael J. Wysocki 		 * Tasks are frozen and the notifiers have been called with
3936c961dfbSRafael J. Wysocki 		 * PM_HIBERNATION_PREPARE
3946c961dfbSRafael J. Wysocki 		 */
3956c961dfbSRafael J. Wysocki 		error = suspend_devices_and_enter(PM_SUSPEND_MEM);
3967bc9b1cfSRafael J. Wysocki 		data->ready = false;
3979b238205SLuca Tettamanti 		break;
3989b238205SLuca Tettamanti 
399eb57c1cfSRafael J. Wysocki 	case SNAPSHOT_PLATFORM_SUPPORT:
400eb57c1cfSRafael J. Wysocki 		data->platform_support = !!arg;
401eb57c1cfSRafael J. Wysocki 		break;
402eb57c1cfSRafael J. Wysocki 
403eb57c1cfSRafael J. Wysocki 	case SNAPSHOT_POWER_OFF:
404eb57c1cfSRafael J. Wysocki 		if (data->platform_support)
405eb57c1cfSRafael J. Wysocki 			error = hibernation_platform_enter();
406eb57c1cfSRafael J. Wysocki 		break;
407eb57c1cfSRafael J. Wysocki 
40837b2ba12SRafael J. Wysocki 	case SNAPSHOT_SET_SWAP_AREA:
40988a77559SChristoph Hellwig 		error = snapshot_set_swap_area(data, (void __user *)arg);
41037b2ba12SRafael J. Wysocki 		break;
41137b2ba12SRafael J. Wysocki 
4126e1819d6SRafael J. Wysocki 	default:
4136e1819d6SRafael J. Wysocki 		error = -ENOTTY;
4146e1819d6SRafael J. Wysocki 
4156e1819d6SRafael J. Wysocki 	}
41625f2f3daSRafael J. Wysocki 
417942f4015SRafael J. Wysocki 	unlock_device_hotplug();
41855f2503cSPingfan Liu 	mutex_unlock(&system_transition_mutex);
41925f2f3daSRafael J. Wysocki 
4206e1819d6SRafael J. Wysocki 	return error;
4216e1819d6SRafael J. Wysocki }
4226e1819d6SRafael J. Wysocki 
423c336078bSBen Hutchings #ifdef CONFIG_COMPAT
424c336078bSBen Hutchings static long
snapshot_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)425c336078bSBen Hutchings snapshot_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
426c336078bSBen Hutchings {
427c336078bSBen Hutchings 	BUILD_BUG_ON(sizeof(loff_t) != sizeof(compat_loff_t));
428c336078bSBen Hutchings 
429c336078bSBen Hutchings 	switch (cmd) {
430c336078bSBen Hutchings 	case SNAPSHOT_GET_IMAGE_SIZE:
431c336078bSBen Hutchings 	case SNAPSHOT_AVAIL_SWAP_SIZE:
432fba616a4SEric Biggers 	case SNAPSHOT_ALLOC_SWAP_PAGE:
433c336078bSBen Hutchings 	case SNAPSHOT_CREATE_IMAGE:
4340f5c4c6eSChristoph Hellwig 	case SNAPSHOT_SET_SWAP_AREA:
435c336078bSBen Hutchings 		return snapshot_ioctl(file, cmd,
436c336078bSBen Hutchings 				      (unsigned long) compat_ptr(arg));
437c336078bSBen Hutchings 	default:
438c336078bSBen Hutchings 		return snapshot_ioctl(file, cmd, arg);
439c336078bSBen Hutchings 	}
440c336078bSBen Hutchings }
441c336078bSBen Hutchings #endif /* CONFIG_COMPAT */
442c336078bSBen Hutchings 
44315ad7cdcSHelge Deller static const struct file_operations snapshot_fops = {
4446e1819d6SRafael J. Wysocki 	.open = snapshot_open,
4456e1819d6SRafael J. Wysocki 	.release = snapshot_release,
4466e1819d6SRafael J. Wysocki 	.read = snapshot_read,
4476e1819d6SRafael J. Wysocki 	.write = snapshot_write,
4486e1819d6SRafael J. Wysocki 	.llseek = no_llseek,
44952d11025SAlan Cox 	.unlocked_ioctl = snapshot_ioctl,
450c336078bSBen Hutchings #ifdef CONFIG_COMPAT
451c336078bSBen Hutchings 	.compat_ioctl = snapshot_compat_ioctl,
452c336078bSBen Hutchings #endif
4536e1819d6SRafael J. Wysocki };
4546e1819d6SRafael J. Wysocki 
4556e1819d6SRafael J. Wysocki static struct miscdevice snapshot_device = {
4566e1819d6SRafael J. Wysocki 	.minor = SNAPSHOT_MINOR,
4576e1819d6SRafael J. Wysocki 	.name = "snapshot",
4586e1819d6SRafael J. Wysocki 	.fops = &snapshot_fops,
4596e1819d6SRafael J. Wysocki };
4606e1819d6SRafael J. Wysocki 
snapshot_device_init(void)4616e1819d6SRafael J. Wysocki static int __init snapshot_device_init(void)
4626e1819d6SRafael J. Wysocki {
4636e1819d6SRafael J. Wysocki 	return misc_register(&snapshot_device);
4646e1819d6SRafael J. Wysocki };
4656e1819d6SRafael J. Wysocki 
4666e1819d6SRafael J. Wysocki device_initcall(snapshot_device_init);
467