diff options
Diffstat (limited to 'kernel/power')
-rw-r--r-- | kernel/power/Kconfig | 11 | ||||
-rw-r--r-- | kernel/power/disk.c | 156 | ||||
-rw-r--r-- | kernel/power/main.c | 48 | ||||
-rw-r--r-- | kernel/power/power.h | 21 | ||||
-rw-r--r-- | kernel/power/process.c | 141 | ||||
-rw-r--r-- | kernel/power/snapshot.c | 53 | ||||
-rw-r--r-- | kernel/power/swsusp.c | 33 | ||||
-rw-r--r-- | kernel/power/user.c | 4 |
8 files changed, 310 insertions, 157 deletions
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 14b0e10dc95c..8e186c678149 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -44,17 +44,6 @@ config PM_VERBOSE ---help--- This option enables verbose messages from the Power Management code. -config DISABLE_CONSOLE_SUSPEND - bool "Keep console(s) enabled during suspend/resume (DANGEROUS)" - depends on PM_DEBUG && PM_SLEEP - default n - ---help--- - This option turns off the console suspend mechanism that prevents - debug messages from reaching the console during the suspend/resume - operations. This may be helpful when debugging device drivers' - suspend/resume routines, but may itself lead to problems, for example - if netconsole is used. - config PM_TRACE bool "Suspend/resume event tracing" depends on PM_DEBUG && X86 && PM_SLEEP && EXPERIMENTAL diff --git a/kernel/power/disk.c b/kernel/power/disk.c index eb72255b5c86..8b15f777010a 100644 --- a/kernel/power/disk.c +++ b/kernel/power/disk.c @@ -45,17 +45,18 @@ enum { static int hibernation_mode = HIBERNATION_SHUTDOWN; -static struct hibernation_ops *hibernation_ops; +static struct platform_hibernation_ops *hibernation_ops; /** * hibernation_set_ops - set the global hibernate operations * @ops: the hibernation operations to use in subsequent hibernation transitions */ -void hibernation_set_ops(struct hibernation_ops *ops) +void hibernation_set_ops(struct platform_hibernation_ops *ops) { - if (ops && !(ops->prepare && ops->enter && ops->finish - && ops->pre_restore && ops->restore_cleanup)) { + if (ops && !(ops->start && ops->pre_snapshot && ops->finish + && ops->prepare && ops->enter && ops->pre_restore + && ops->restore_cleanup)) { WARN_ON(1); return; } @@ -69,16 +70,37 @@ void hibernation_set_ops(struct hibernation_ops *ops) mutex_unlock(&pm_mutex); } +/** + * platform_start - tell the platform driver that we're starting + * hibernation + */ + +static int platform_start(int platform_mode) +{ + return (platform_mode && hibernation_ops) ? + hibernation_ops->start() : 0; +} /** - * platform_prepare - prepare the machine for hibernation using the + * platform_pre_snapshot - prepare the machine for hibernation using the * platform driver if so configured and return an error code if it fails */ -static int platform_prepare(int platform_mode) +static int platform_pre_snapshot(int platform_mode) { return (platform_mode && hibernation_ops) ? - hibernation_ops->prepare() : 0; + hibernation_ops->pre_snapshot() : 0; +} + +/** + * platform_leave - prepare the machine for switching to the normal mode + * of operation using the platform driver (called with interrupts disabled) + */ + +static void platform_leave(int platform_mode) +{ + if (platform_mode && hibernation_ops) + hibernation_ops->leave(); } /** @@ -118,6 +140,51 @@ static void platform_restore_cleanup(int platform_mode) } /** + * create_image - freeze devices that need to be frozen with interrupts + * off, create the hibernation image and thaw those devices. Control + * reappears in this routine after a restore. + */ + +int create_image(int platform_mode) +{ + int error; + + error = arch_prepare_suspend(); + if (error) + return error; + + local_irq_disable(); + /* At this point, device_suspend() has been called, but *not* + * device_power_down(). We *must* call device_power_down() now. + * Otherwise, drivers for some devices (e.g. interrupt controllers) + * become desynchronized with the actual state of the hardware + * at resume time, and evil weirdness ensues. + */ + error = device_power_down(PMSG_FREEZE); + if (error) { + printk(KERN_ERR "Some devices failed to power down, " + KERN_ERR "aborting suspend\n"); + goto Enable_irqs; + } + + save_processor_state(); + error = swsusp_arch_suspend(); + if (error) + printk(KERN_ERR "Error %d while creating the image\n", error); + /* Restore control flow magically appears here */ + restore_processor_state(); + if (!in_suspend) + platform_leave(platform_mode); + /* NOTE: device_power_up() is just a resume() for devices + * that suspended with irqs off ... no overall powerup. + */ + device_power_up(); + Enable_irqs: + local_irq_enable(); + return error; +} + +/** * hibernation_snapshot - quiesce devices and create the hibernation * snapshot image. * @platform_mode - if set, use the platform driver, if available, to @@ -135,12 +202,16 @@ int hibernation_snapshot(int platform_mode) if (error) return error; + error = platform_start(platform_mode); + if (error) + return error; + suspend_console(); error = device_suspend(PMSG_FREEZE); if (error) goto Resume_console; - error = platform_prepare(platform_mode); + error = platform_pre_snapshot(platform_mode); if (error) goto Resume_devices; @@ -148,7 +219,7 @@ int hibernation_snapshot(int platform_mode) if (!error) { if (hibernation_mode != HIBERNATION_TEST) { in_suspend = 1; - error = swsusp_suspend(); + error = create_image(platform_mode); /* Control returns here after successful restore */ } else { printk("swsusp debug: Waiting for 5 seconds.\n"); @@ -207,21 +278,50 @@ int hibernation_platform_enter(void) { int error; - if (hibernation_ops) { - kernel_shutdown_prepare(SYSTEM_SUSPEND_DISK); - /* - * We have cancelled the power transition by running - * hibernation_ops->finish() before saving the image, so we - * should let the firmware know that we're going to enter the - * sleep state after all - */ - error = hibernation_ops->prepare(); - sysdev_shutdown(); - if (!error) - error = hibernation_ops->enter(); - } else { - error = -ENOSYS; + if (!hibernation_ops) + return -ENOSYS; + + /* + * We have cancelled the power transition by running + * hibernation_ops->finish() before saving the image, so we should let + * the firmware know that we're going to enter the sleep state after all + */ + error = hibernation_ops->start(); + if (error) + return error; + + suspend_console(); + error = device_suspend(PMSG_SUSPEND); + if (error) + goto Resume_console; + + error = hibernation_ops->prepare(); + if (error) + goto Resume_devices; + + error = disable_nonboot_cpus(); + if (error) + goto Finish; + + local_irq_disable(); + error = device_power_down(PMSG_SUSPEND); + if (!error) { + hibernation_ops->enter(); + /* We should never get here */ + while (1); } + local_irq_enable(); + + /* + * We don't need to reenable the nonboot CPUs or resume consoles, since + * the system is going to be halted anyway. + */ + Finish: + hibernation_ops->finish(); + Resume_devices: + device_resume(); + Resume_console: + resume_console(); return error; } @@ -238,14 +338,14 @@ static void power_down(void) case HIBERNATION_TEST: case HIBERNATION_TESTPROC: break; - case HIBERNATION_SHUTDOWN: - kernel_power_off(); - break; case HIBERNATION_REBOOT: kernel_restart(NULL); break; case HIBERNATION_PLATFORM: hibernation_platform_enter(); + case HIBERNATION_SHUTDOWN: + kernel_power_off(); + break; } kernel_halt(); /* @@ -298,6 +398,10 @@ int hibernate(void) if (error) goto Exit; + printk("Syncing filesystems ... "); + sys_sync(); + printk("done.\n"); + error = prepare_processes(); if (error) goto Finish; diff --git a/kernel/power/main.c b/kernel/power/main.c index 350b485b3b60..3cdf95b1dc92 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -20,6 +20,7 @@ #include <linux/resume-trace.h> #include <linux/freezer.h> #include <linux/vmstat.h> +#include <linux/syscalls.h> #include "power.h" @@ -32,39 +33,32 @@ DEFINE_MUTEX(pm_mutex); /* This is just an arbitrary number */ #define FREE_PAGE_NUMBER (100) -struct pm_ops *pm_ops; +static struct platform_suspend_ops *suspend_ops; /** - * pm_set_ops - Set the global power method table. + * suspend_set_ops - Set the global suspend method table. * @ops: Pointer to ops structure. */ -void pm_set_ops(struct pm_ops * ops) +void suspend_set_ops(struct platform_suspend_ops *ops) { mutex_lock(&pm_mutex); - pm_ops = ops; + suspend_ops = ops; mutex_unlock(&pm_mutex); } /** - * pm_valid_only_mem - generic memory-only valid callback + * suspend_valid_only_mem - generic memory-only valid callback * - * pm_ops drivers that implement mem suspend only and only need + * Platform drivers that implement mem suspend only and only need * to check for that in their .valid callback can use this instead * of rolling their own .valid callback. */ -int pm_valid_only_mem(suspend_state_t state) +int suspend_valid_only_mem(suspend_state_t state) { return state == PM_SUSPEND_MEM; } - -static inline void pm_finish(suspend_state_t state) -{ - if (pm_ops->finish) - pm_ops->finish(state); -} - /** * suspend_prepare - Do prep work before entering low-power state. * @@ -76,7 +70,7 @@ static int suspend_prepare(void) int error; unsigned int free_pages; - if (!pm_ops || !pm_ops->enter) + if (!suspend_ops || !suspend_ops->enter) return -EPERM; error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); @@ -128,7 +122,7 @@ void __attribute__ ((weak)) arch_suspend_enable_irqs(void) * * This function should be called after devices have been suspended. */ -int suspend_enter(suspend_state_t state) +static int suspend_enter(suspend_state_t state) { int error = 0; @@ -139,7 +133,7 @@ int suspend_enter(suspend_state_t state) printk(KERN_ERR "Some devices failed to power down\n"); goto Done; } - error = pm_ops->enter(state); + error = suspend_ops->enter(state); device_power_up(); Done: arch_suspend_enable_irqs(); @@ -156,11 +150,11 @@ int suspend_devices_and_enter(suspend_state_t state) { int error; - if (!pm_ops) + if (!suspend_ops) return -ENOSYS; - if (pm_ops->set_target) { - error = pm_ops->set_target(state); + if (suspend_ops->set_target) { + error = suspend_ops->set_target(state); if (error) return error; } @@ -170,8 +164,8 @@ int suspend_devices_and_enter(suspend_state_t state) printk(KERN_ERR "Some devices failed to suspend\n"); goto Resume_console; } - if (pm_ops->prepare) { - error = pm_ops->prepare(state); + if (suspend_ops->prepare) { + error = suspend_ops->prepare(); if (error) goto Resume_devices; } @@ -180,7 +174,8 @@ int suspend_devices_and_enter(suspend_state_t state) suspend_enter(state); enable_nonboot_cpus(); - pm_finish(state); + if (suspend_ops->finish) + suspend_ops->finish(); Resume_devices: device_resume(); Resume_console: @@ -214,7 +209,7 @@ static inline int valid_state(suspend_state_t state) /* All states need lowlevel support and need to be valid * to the lowlevel implementation, no valid callback * implies that none are valid. */ - if (!pm_ops || !pm_ops->valid || !pm_ops->valid(state)) + if (!suspend_ops || !suspend_ops->valid || !suspend_ops->valid(state)) return 0; return 1; } @@ -236,9 +231,14 @@ static int enter_state(suspend_state_t state) if (!valid_state(state)) return -ENODEV; + if (!mutex_trylock(&pm_mutex)) return -EBUSY; + printk("Syncing filesystems ... "); + sys_sync(); + printk("done.\n"); + pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); if ((error = suspend_prepare())) goto Unlock; diff --git a/kernel/power/power.h b/kernel/power/power.h index 95fbf2dd3fe3..195dc4611764 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -11,14 +11,32 @@ struct swsusp_info { unsigned long size; } __attribute__((aligned(PAGE_SIZE))); +#ifdef CONFIG_HIBERNATION +#ifdef CONFIG_ARCH_HIBERNATION_HEADER +/* Maximum size of architecture specific data in a hibernation header */ +#define MAX_ARCH_HEADER_SIZE (sizeof(struct new_utsname) + 4) +extern int arch_hibernation_header_save(void *addr, unsigned int max_size); +extern int arch_hibernation_header_restore(void *addr); + +static inline int init_header_complete(struct swsusp_info *info) +{ + return arch_hibernation_header_save(info, MAX_ARCH_HEADER_SIZE); +} + +static inline char *check_image_kernel(struct swsusp_info *info) +{ + return arch_hibernation_header_restore(info) ? + "architecture specific data" : NULL; +} +#endif /* CONFIG_ARCH_HIBERNATION_HEADER */ -#ifdef CONFIG_HIBERNATION /* * Keep some memory free so that I/O operations can succeed without paging * [Might this be more than 4 MB?] */ #define PAGES_FOR_IO ((4096 * 1024) >> PAGE_SHIFT) + /* * Keep 1 MB of memory free so that device drivers can allocate some pages in * their .suspend() routines without breaking the suspend to disk. @@ -165,7 +183,6 @@ extern int swsusp_swap_in_use(void); extern int swsusp_check(void); extern int swsusp_shrink_memory(void); extern void swsusp_free(void); -extern int swsusp_suspend(void); extern int swsusp_resume(void); extern int swsusp_read(unsigned int *flags_p); extern int swsusp_write(unsigned int flags); diff --git a/kernel/power/process.c b/kernel/power/process.c index 3434940a3df1..6533923e711b 100644 --- a/kernel/power/process.c +++ b/kernel/power/process.c @@ -75,21 +75,79 @@ void refrigerator(void) __set_current_state(save); } -static void freeze_task(struct task_struct *p) +static void fake_signal_wake_up(struct task_struct *p, int resume) { unsigned long flags; - if (!freezing(p)) { + spin_lock_irqsave(&p->sighand->siglock, flags); + signal_wake_up(p, resume); + spin_unlock_irqrestore(&p->sighand->siglock, flags); +} + +static void send_fake_signal(struct task_struct *p) +{ + if (p->state == TASK_STOPPED) + force_sig_specific(SIGSTOP, p); + fake_signal_wake_up(p, p->state == TASK_STOPPED); +} + +static int has_mm(struct task_struct *p) +{ + return (p->mm && !(p->flags & PF_BORROWED_MM)); +} + +/** + * freeze_task - send a freeze request to given task + * @p: task to send the request to + * @with_mm_only: if set, the request will only be sent if the task has its + * own mm + * Return value: 0, if @with_mm_only is set and the task has no mm of its + * own or the task is frozen, 1, otherwise + * + * The freeze request is sent by seting the tasks's TIF_FREEZE flag and + * either sending a fake signal to it or waking it up, depending on whether + * or not it has its own mm (ie. it is a user land task). If @with_mm_only + * is set and the task has no mm of its own (ie. it is a kernel thread), + * its TIF_FREEZE flag should not be set. + * + * The task_lock() is necessary to prevent races with exit_mm() or + * use_mm()/unuse_mm() from occuring. + */ +static int freeze_task(struct task_struct *p, int with_mm_only) +{ + int ret = 1; + + task_lock(p); + if (freezing(p)) { + if (has_mm(p)) { + if (!signal_pending(p)) + fake_signal_wake_up(p, 0); + } else { + if (with_mm_only) + ret = 0; + else + wake_up_state(p, TASK_INTERRUPTIBLE); + } + } else { rmb(); - if (!frozen(p)) { - set_freeze_flag(p); - if (p->state == TASK_STOPPED) - force_sig_specific(SIGSTOP, p); - spin_lock_irqsave(&p->sighand->siglock, flags); - signal_wake_up(p, p->state == TASK_STOPPED); - spin_unlock_irqrestore(&p->sighand->siglock, flags); + if (frozen(p)) { + ret = 0; + } else { + if (has_mm(p)) { + set_freeze_flag(p); + send_fake_signal(p); + } else { + if (with_mm_only) { + ret = 0; + } else { + set_freeze_flag(p); + wake_up_state(p, TASK_INTERRUPTIBLE); + } + } } } + task_unlock(p); + return ret; } static void cancel_freezing(struct task_struct *p) @@ -110,6 +168,11 @@ static int try_to_freeze_tasks(int freeze_user_space) struct task_struct *g, *p; unsigned long end_time; unsigned int todo; + struct timeval start, end; + s64 elapsed_csecs64; + unsigned int elapsed_csecs; + + do_gettimeofday(&start); end_time = jiffies + TIMEOUT; do { @@ -119,31 +182,14 @@ static int try_to_freeze_tasks(int freeze_user_space) if (frozen(p) || !freezeable(p)) continue; - if (freeze_user_space) { - if (p->state == TASK_TRACED && - frozen(p->parent)) { - cancel_freezing(p); - continue; - } - /* - * Kernel threads should not have TIF_FREEZE set - * at this point, so we must ensure that either - * p->mm is not NULL *and* PF_BORROWED_MM is - * unset, or TIF_FRREZE is left unset. - * The task_lock() is necessary to prevent races - * with exit_mm() or use_mm()/unuse_mm() from - * occuring. - */ - task_lock(p); - if (!p->mm || (p->flags & PF_BORROWED_MM)) { - task_unlock(p); - continue; - } - freeze_task(p); - task_unlock(p); - } else { - freeze_task(p); + if (p->state == TASK_TRACED && frozen(p->parent)) { + cancel_freezing(p); + continue; } + + if (!freeze_task(p, freeze_user_space)) + continue; + if (!freezer_should_skip(p)) todo++; } while_each_thread(g, p); @@ -153,6 +199,11 @@ static int try_to_freeze_tasks(int freeze_user_space) break; } while (todo); + do_gettimeofday(&end); + elapsed_csecs64 = timeval_to_ns(&end) - timeval_to_ns(&start); + do_div(elapsed_csecs64, NSEC_PER_SEC / 100); + elapsed_csecs = elapsed_csecs64; + if (todo) { /* This does not unfreeze processes that are already frozen * (we have slightly ugly calling convention in that respect, @@ -160,10 +211,9 @@ static int try_to_freeze_tasks(int freeze_user_space) * but it cleans up leftover PF_FREEZE requests. */ printk("\n"); - printk(KERN_ERR "Freezing of %s timed out after %d seconds " + printk(KERN_ERR "Freezing of tasks failed after %d.%02d seconds " "(%d tasks refusing to freeze):\n", - freeze_user_space ? "user space " : "tasks ", - TIMEOUT / HZ, todo); + elapsed_csecs / 100, elapsed_csecs % 100, todo); show_state(); read_lock(&tasklist_lock); do_each_thread(g, p) { @@ -174,6 +224,9 @@ static int try_to_freeze_tasks(int freeze_user_space) task_unlock(p); } while_each_thread(g, p); read_unlock(&tasklist_lock); + } else { + printk("(elapsed %d.%02d seconds) ", elapsed_csecs / 100, + elapsed_csecs % 100); } return todo ? -EBUSY : 0; @@ -186,19 +239,21 @@ int freeze_processes(void) { int error; - printk("Stopping tasks ... "); + printk("Freezing user space processes ... "); error = try_to_freeze_tasks(FREEZER_USER_SPACE); if (error) - return error; + goto Exit; + printk("done.\n"); - sys_sync(); + printk("Freezing remaining freezable tasks ... "); error = try_to_freeze_tasks(FREEZER_KERNEL_THREADS); if (error) - return error; - - printk("done.\n"); + goto Exit; + printk("done."); + Exit: BUG_ON(in_atomic()); - return 0; + printk("\n"); + return error; } static void thaw_tasks(int thaw_user_space) diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index a686590d88c1..ccc95ac07bed 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -1239,17 +1239,39 @@ asmlinkage int swsusp_save(void) return 0; } -static void init_header(struct swsusp_info *info) +#ifndef CONFIG_ARCH_HIBERNATION_HEADER +static int init_header_complete(struct swsusp_info *info) { - memset(info, 0, sizeof(struct swsusp_info)); + memcpy(&info->uts, init_utsname(), sizeof(struct new_utsname)); info->version_code = LINUX_VERSION_CODE; + return 0; +} + +static char *check_image_kernel(struct swsusp_info *info) +{ + if (info->version_code != LINUX_VERSION_CODE) + return "kernel version"; + if (strcmp(info->uts.sysname,init_utsname()->sysname)) + return "system type"; + if (strcmp(info->uts.release,init_utsname()->release)) + return "kernel release"; + if (strcmp(info->uts.version,init_utsname()->version)) + return "version"; + if (strcmp(info->uts.machine,init_utsname()->machine)) + return "machine"; + return NULL; +} +#endif /* CONFIG_ARCH_HIBERNATION_HEADER */ + +static int init_header(struct swsusp_info *info) +{ + memset(info, 0, sizeof(struct swsusp_info)); info->num_physpages = num_physpages; - memcpy(&info->uts, init_utsname(), sizeof(struct new_utsname)); - info->cpus = num_online_cpus(); info->image_pages = nr_copy_pages; info->pages = nr_copy_pages + nr_meta_pages + 1; info->size = info->pages; info->size <<= PAGE_SHIFT; + return init_header_complete(info); } /** @@ -1303,7 +1325,11 @@ int snapshot_read_next(struct snapshot_handle *handle, size_t count) return -ENOMEM; } if (!handle->offset) { - init_header((struct swsusp_info *)buffer); + int error; + + error = init_header((struct swsusp_info *)buffer); + if (error) + return error; handle->buffer = buffer; memory_bm_position_reset(&orig_bm); memory_bm_position_reset(©_bm); @@ -1394,22 +1420,13 @@ duplicate_memory_bitmap(struct memory_bitmap *dst, struct memory_bitmap *src) } } -static inline int check_header(struct swsusp_info *info) +static int check_header(struct swsusp_info *info) { - char *reason = NULL; + char *reason; - if (info->version_code != LINUX_VERSION_CODE) - reason = "kernel version"; - if (info->num_physpages != num_physpages) + reason = check_image_kernel(info); + if (!reason && info->num_physpages != num_physpages) reason = "memory size"; - if (strcmp(info->uts.sysname,init_utsname()->sysname)) - reason = "system type"; - if (strcmp(info->uts.release,init_utsname()->release)) - reason = "kernel release"; - if (strcmp(info->uts.version,init_utsname()->version)) - reason = "version"; - if (strcmp(info->uts.machine,init_utsname()->machine)) - reason = "machine"; if (reason) { printk(KERN_ERR "swsusp: Resume mismatch: %s\n", reason); return -EPERM; diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c index 5da304c8f1f6..e1722d3155f1 100644 --- a/kernel/power/swsusp.c +++ b/kernel/power/swsusp.c @@ -270,39 +270,6 @@ int swsusp_shrink_memory(void) return 0; } -int swsusp_suspend(void) -{ - int error; - - if ((error = arch_prepare_suspend())) - return error; - - local_irq_disable(); - /* At this point, device_suspend() has been called, but *not* - * device_power_down(). We *must* device_power_down() now. - * Otherwise, drivers for some devices (e.g. interrupt controllers) - * become desynchronized with the actual state of the hardware - * at resume time, and evil weirdness ensues. - */ - if ((error = device_power_down(PMSG_FREEZE))) { - printk(KERN_ERR "Some devices failed to power down, aborting suspend\n"); - goto Enable_irqs; - } - - save_processor_state(); - if ((error = swsusp_arch_suspend())) - printk(KERN_ERR "Error %d suspending\n", error); - /* Restore control flow magically appears here */ - restore_processor_state(); - /* NOTE: device_power_up() is just a resume() for devices - * that suspended with irqs off ... no overall powerup. - */ - device_power_up(); - Enable_irqs: - local_irq_enable(); - return error; -} - int swsusp_resume(void) { int error; diff --git a/kernel/power/user.c b/kernel/power/user.c index bd0723a7df3f..5bd321bcbb75 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -153,6 +153,10 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp, mutex_lock(&pm_mutex); error = pm_notifier_call_chain(PM_HIBERNATION_PREPARE); if (!error) { + printk("Syncing filesystems ... "); + sys_sync(); + printk("done.\n"); + error = freeze_processes(); if (error) thaw_processes(); |