diff options
Diffstat (limited to 'kernel/power/user.c')
| -rw-r--r-- | kernel/power/user.c | 405 |
1 files changed, 275 insertions, 130 deletions
diff --git a/kernel/power/user.c b/kernel/power/user.c index 3f1539fbe48..526e8911460 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -11,6 +11,7 @@ #include <linux/suspend.h> #include <linux/syscalls.h> +#include <linux/reboot.h> #include <linux/string.h> #include <linux/device.h> #include <linux/miscdevice.h> @@ -19,67 +20,115 @@ #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 <asm/uaccess.h> #include "power.h" + #define SNAPSHOT_MINOR 231 static struct snapshot_data { struct snapshot_handle handle; int swap; - struct bitmap_page *bitmap; int mode; - char frozen; - char ready; + bool frozen; + bool ready; + bool platform_support; + bool free_bitmaps; } snapshot_state; -static atomic_t device_available = ATOMIC_INIT(1); +atomic_t snapshot_device_available = ATOMIC_INIT(1); static int snapshot_open(struct inode *inode, struct file *filp) { struct snapshot_data *data; + int error; - if (!atomic_add_unless(&device_available, -1, 0)) - return -EBUSY; + if (!hibernation_available()) + return -EPERM; - if ((filp->f_flags & O_ACCMODE) == O_RDWR) - return -ENOSYS; + lock_system_sleep(); + if (!atomic_add_unless(&snapshot_device_available, -1, 0)) { + error = -EBUSY; + goto Unlock; + } + + if ((filp->f_flags & O_ACCMODE) == O_RDWR) { + atomic_inc(&snapshot_device_available); + 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) { - data->swap = swsusp_resume_device ? swap_type_of(swsusp_resume_device) : -1; + /* Hibernating. The image device should be accessible. */ + data->swap = swsusp_resume_device ? + swap_type_of(swsusp_resume_device, 0, NULL) : -1; data->mode = O_RDONLY; + data->free_bitmaps = false; + error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE); + if (error) + pm_notifier_call_chain(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(PM_RESTORE_PREPARE); + if (!error) { + error = create_basic_memory_bitmaps(); + data->free_bitmaps = !error; + } + if (error) + pm_notifier_call_chain(PM_POST_RESTORE); } - data->bitmap = NULL; - data->frozen = 0; - data->ready = 0; + if (error) + atomic_inc(&snapshot_device_available); - return 0; + data->frozen = false; + data->ready = false; + data->platform_support = false; + + 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; - free_all_swap_pages(data->swap, data->bitmap); - free_bitmap(data->bitmap); + free_all_swap_pages(data->swap); if (data->frozen) { - down(&pm_sem); + pm_restore_gfp_mask(); + free_basic_memory_bitmaps(); thaw_processes(); - enable_nonboot_cpus(); - up(&pm_sem); + } else if (data->free_bitmaps) { + free_basic_memory_bitmaps(); } - atomic_inc(&device_available); + pm_notifier_call_chain(data->mode == O_RDONLY ? + PM_POST_HIBERNATION : PM_POST_RESTORE); + atomic_inc(&snapshot_device_available); + + unlock_system_sleep(); + return 0; } @@ -88,15 +137,31 @@ static ssize_t snapshot_read(struct file *filp, char __user *buf, { struct snapshot_data *data; ssize_t res; + loff_t pg_offp = *offp & ~PAGE_MASK; + + lock_system_sleep(); data = filp->private_data; - res = snapshot_read_next(&data->handle, count); - if (res > 0) { - if (copy_to_user(buf, data_of(data->handle), res)) - res = -EFAULT; - else - *offp = data->handle.offset; + 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; } @@ -105,24 +170,37 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf, { struct snapshot_data *data; ssize_t res; + loff_t pg_offp = *offp & ~PAGE_MASK; + + lock_system_sleep(); data = filp->private_data; - res = snapshot_write_next(&data->handle, count); - if (res > 0) { - if (copy_from_user(data_of(data->handle), buf, res)) - res = -EFAULT; - else - *offp = data->handle.offset; + + if (!pg_offp) { + res = snapshot_write_next(&data->handle); + if (res <= 0) + goto unlock; + } else { + res = PAGE_SIZE - pg_offp; } + + 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; } -static int snapshot_ioctl(struct inode *inode, struct file *filp, - unsigned int cmd, unsigned long arg) +static long snapshot_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) { int error = 0; struct snapshot_data *data; - loff_t offset, avail; + loff_t size; + sector_t offset; if (_IOC_TYPE(cmd) != SNAPSHOT_IOC_MAGIC) return -ENOTTY; @@ -131,6 +209,10 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, if (!capable(CAP_SYS_ADMIN)) return -EPERM; + if (!mutex_trylock(&pm_mutex)) + return -EBUSY; + + lock_device_hotplug(); data = filp->private_data; switch (cmd) { @@ -138,97 +220,98 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, case SNAPSHOT_FREEZE: if (data->frozen) break; - down(&pm_sem); - disable_nonboot_cpus(); - if (freeze_processes()) { + + printk("Syncing filesystems ... "); + sys_sync(); + printk("done.\n"); + + error = freeze_processes(); + if (error) + break; + + error = create_basic_memory_bitmaps(); + if (error) thaw_processes(); - enable_nonboot_cpus(); - error = -EBUSY; - } - up(&pm_sem); - if (!error) - data->frozen = 1; + else + data->frozen = true; + break; case SNAPSHOT_UNFREEZE: - if (!data->frozen) + if (!data->frozen || data->ready) break; - down(&pm_sem); + pm_restore_gfp_mask(); + free_basic_memory_bitmaps(); + data->free_bitmaps = false; thaw_processes(); - enable_nonboot_cpus(); - up(&pm_sem); - data->frozen = 0; + data->frozen = false; break; - case SNAPSHOT_ATOMIC_SNAPSHOT: + case SNAPSHOT_CREATE_IMAGE: if (data->mode != O_RDONLY || !data->frozen || data->ready) { error = -EPERM; break; } - down(&pm_sem); - /* Free memory before shutting down devices. */ - error = swsusp_shrink_memory(); + pm_restore_gfp_mask(); + error = hibernation_snapshot(data->platform_support); if (!error) { - error = device_suspend(PMSG_FREEZE); - if (!error) { - in_suspend = 1; - error = swsusp_suspend(); - device_resume(); - } + error = put_user(in_suspend, (int __user *)arg); + data->ready = !freezer_test_done && !error; + freezer_test_done = false; } - up(&pm_sem); - if (!error) - error = put_user(in_suspend, (unsigned int __user *)arg); - if (!error) - data->ready = 1; 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; } - down(&pm_sem); - pm_prepare_console(); - error = device_suspend(PMSG_FREEZE); - if (!error) { - error = swsusp_resume(); - device_resume(); - } - pm_restore_console(); - up(&pm_sem); + error = hibernation_restore(data->platform_support); break; case SNAPSHOT_FREE: swsusp_free(); memset(&data->handle, 0, sizeof(struct snapshot_handle)); - data->ready = 0; + 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_SET_IMAGE_SIZE: + case SNAPSHOT_PREF_IMAGE_SIZE: image_size = arg; break; - case SNAPSHOT_AVAIL_SWAP: - avail = count_swap_pages(data->swap, 1); - avail <<= PAGE_SHIFT; - error = put_user(avail, (loff_t __user *)arg); + 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_GET_SWAP_PAGE: + 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; } - if (!data->bitmap) { - data->bitmap = alloc_bitmap(count_swap_pages(data->swap, 0)); - if (!data->bitmap) { - error = -ENOMEM; - break; - } - } - offset = alloc_swap_page(data->swap, data->bitmap); + offset = alloc_swapdev_block(data->swap); if (offset) { offset <<= PAGE_SHIFT; error = put_user(offset, (loff_t __user *)arg); @@ -242,81 +325,143 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, error = -ENODEV; break; } - free_all_swap_pages(data->swap, data->bitmap); - free_bitmap(data->bitmap); - data->bitmap = NULL; + 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_SET_SWAP_FILE: - if (!data->bitmap) { + 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: + if (swsusp_swap_in_use()) { + error = -EPERM; + } else { + struct resume_swap_area swap_area; + dev_t swdev; + + error = copy_from_user(&swap_area, (void __user *)arg, + sizeof(struct resume_swap_area)); + if (error) { + error = -EFAULT; + break; + } + /* * User space encodes device types as two-byte values, * so we need to recode them */ - if (old_decode_dev(arg)) { - data->swap = swap_type_of(old_decode_dev(arg)); + swdev = new_decode_dev(swap_area.dev); + if (swdev) { + offset = swap_area.offset; + data->swap = swap_type_of(swdev, offset, NULL); if (data->swap < 0) error = -ENODEV; } else { data->swap = -1; error = -EINVAL; } - } else { - error = -EPERM; } break; - case SNAPSHOT_S2RAM: - if (!data->frozen) { - error = -EPERM; - break; - } + default: + error = -ENOTTY; - if (down_trylock(&pm_sem)) { - error = -EBUSY; - break; - } + } - if (pm_ops->prepare) { - error = pm_ops->prepare(PM_SUSPEND_MEM); - if (error) - goto OutS3; - } + unlock_device_hotplug(); + mutex_unlock(&pm_mutex); - /* Put devices to sleep */ - error = device_suspend(PMSG_SUSPEND); - if (error) { - printk(KERN_ERR "Failed to suspend some devices.\n"); - } else { - /* Enter S3, system is already frozen */ - suspend_enter(PM_SUSPEND_MEM); + return error; +} - /* Wake up devices */ - device_resume(); - } +#ifdef CONFIG_COMPAT - if (pm_ops->finish) - pm_ops->finish(PM_SUSPEND_MEM); +struct compat_resume_swap_area { + compat_loff_t offset; + u32 dev; +} __packed; -OutS3: - up(&pm_sem); - break; +static long +snapshot_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + BUILD_BUG_ON(sizeof(loff_t) != sizeof(compat_loff_t)); - default: - error = -ENOTTY; + switch (cmd) { + case SNAPSHOT_GET_IMAGE_SIZE: + case SNAPSHOT_AVAIL_SWAP_SIZE: + case SNAPSHOT_ALLOC_SWAP_PAGE: { + compat_loff_t __user *uoffset = compat_ptr(arg); + loff_t offset; + mm_segment_t old_fs; + int err; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = snapshot_ioctl(file, cmd, (unsigned long) &offset); + set_fs(old_fs); + if (!err && put_user(offset, uoffset)) + err = -EFAULT; + return err; + } + case SNAPSHOT_CREATE_IMAGE: + return snapshot_ioctl(file, cmd, + (unsigned long) compat_ptr(arg)); + + case SNAPSHOT_SET_SWAP_AREA: { + struct compat_resume_swap_area __user *u_swap_area = + compat_ptr(arg); + struct resume_swap_area swap_area; + mm_segment_t old_fs; + int err; + + err = get_user(swap_area.offset, &u_swap_area->offset); + err |= get_user(swap_area.dev, &u_swap_area->dev); + if (err) + return -EFAULT; + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = snapshot_ioctl(file, SNAPSHOT_SET_SWAP_AREA, + (unsigned long) &swap_area); + set_fs(old_fs); + return err; } - return error; + default: + return snapshot_ioctl(file, cmd, arg); + } } -static struct file_operations snapshot_fops = { +#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, - .ioctl = snapshot_ioctl, + .unlocked_ioctl = snapshot_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = snapshot_compat_ioctl, +#endif }; static struct miscdevice snapshot_device = { |
