Skip to content

Commit

Permalink
[PATCH] Disable CPU hotplug during suspend
Browse files Browse the repository at this point in the history
The current suspend code has to be run on one CPU, so we use the CPU
hotplug to take the non-boot CPUs offline on SMP machines.  However, we
should also make sure that these CPUs will not be enabled by someone else
after we have disabled them.

The functions disable_nonboot_cpus() and enable_nonboot_cpus() are moved to
kernel/cpu.c, because they now refer to some stuff in there that should
better be static.  Also it's better if disable_nonboot_cpus() returns an
error instead of panicking if something goes wrong, and
enable_nonboot_cpus() has no reason to panic(), because the CPUs may have
been enabled by the userland before it tries to take them online.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
  • Loading branch information
Rafael J. Wysocki authored and Linus Torvalds committed Sep 26, 2006
1 parent e8eff5a commit e3920fb
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 104 deletions.
8 changes: 8 additions & 0 deletions include/linux/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,12 @@ int cpu_down(unsigned int cpu);
static inline int cpu_is_offline(int cpu) { return 0; }
#endif

#ifdef CONFIG_SUSPEND_SMP
extern int disable_nonboot_cpus(void);
extern void enable_nonboot_cpus(void);
#else
static inline int disable_nonboot_cpus(void) { return 0; }
static inline void enable_nonboot_cpus(void) {}
#endif

#endif /* _LINUX_CPU_H_ */
8 changes: 0 additions & 8 deletions include/linux/suspend.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ static inline int software_suspend(void)
}
#endif /* CONFIG_PM */

#ifdef CONFIG_SUSPEND_SMP
extern void disable_nonboot_cpus(void);
extern void enable_nonboot_cpus(void);
#else
static inline void disable_nonboot_cpus(void) {}
static inline void enable_nonboot_cpus(void) {}
#endif

void save_processor_state(void);
void restore_processor_state(void);
struct saved_context;
Expand Down
138 changes: 118 additions & 20 deletions kernel/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ static DEFINE_MUTEX(cpu_bitmask_lock);

static __cpuinitdata BLOCKING_NOTIFIER_HEAD(cpu_chain);

/* If set, cpu_up and cpu_down will return -EBUSY and do nothing.
* Should always be manipulated under cpu_add_remove_lock
*/
static int cpu_hotplug_disabled;

#ifdef CONFIG_HOTPLUG_CPU

/* Crappy recursive lock-takers in cpufreq! Complain loudly about idiots */
Expand Down Expand Up @@ -108,30 +113,25 @@ static int take_cpu_down(void *unused)
return 0;
}

int cpu_down(unsigned int cpu)
/* Requires cpu_add_remove_lock to be held */
static int _cpu_down(unsigned int cpu)
{
int err;
struct task_struct *p;
cpumask_t old_allowed, tmp;

mutex_lock(&cpu_add_remove_lock);
if (num_online_cpus() == 1) {
err = -EBUSY;
goto out;
}
if (num_online_cpus() == 1)
return -EBUSY;

if (!cpu_online(cpu)) {
err = -EINVAL;
goto out;
}
if (!cpu_online(cpu))
return -EINVAL;

err = blocking_notifier_call_chain(&cpu_chain, CPU_DOWN_PREPARE,
(void *)(long)cpu);
if (err == NOTIFY_BAD) {
printk("%s: attempt to take down CPU %u failed\n",
__FUNCTION__, cpu);
err = -EINVAL;
goto out;
return -EINVAL;
}

/* Ensure that we are not runnable on dying cpu */
Expand Down Expand Up @@ -179,22 +179,32 @@ int cpu_down(unsigned int cpu)
err = kthread_stop(p);
out_allowed:
set_cpus_allowed(current, old_allowed);
out:
return err;
}

int cpu_down(unsigned int cpu)
{
int err = 0;

mutex_lock(&cpu_add_remove_lock);
if (cpu_hotplug_disabled)
err = -EBUSY;
else
err = _cpu_down(cpu);

mutex_unlock(&cpu_add_remove_lock);
return err;
}
#endif /*CONFIG_HOTPLUG_CPU*/

int __devinit cpu_up(unsigned int cpu)
/* Requires cpu_add_remove_lock to be held */
static int __devinit _cpu_up(unsigned int cpu)
{
int ret;
void *hcpu = (void *)(long)cpu;

mutex_lock(&cpu_add_remove_lock);
if (cpu_online(cpu) || !cpu_present(cpu)) {
ret = -EINVAL;
goto out;
}
if (cpu_online(cpu) || !cpu_present(cpu))
return -EINVAL;

ret = blocking_notifier_call_chain(&cpu_chain, CPU_UP_PREPARE, hcpu);
if (ret == NOTIFY_BAD) {
Expand All @@ -219,7 +229,95 @@ int __devinit cpu_up(unsigned int cpu)
if (ret != 0)
blocking_notifier_call_chain(&cpu_chain,
CPU_UP_CANCELED, hcpu);

return ret;
}

int __devinit cpu_up(unsigned int cpu)
{
int err = 0;

mutex_lock(&cpu_add_remove_lock);
if (cpu_hotplug_disabled)
err = -EBUSY;
else
err = _cpu_up(cpu);

mutex_unlock(&cpu_add_remove_lock);
return err;
}

#ifdef CONFIG_SUSPEND_SMP
static cpumask_t frozen_cpus;

int disable_nonboot_cpus(void)
{
int cpu, first_cpu, error;

mutex_lock(&cpu_add_remove_lock);
first_cpu = first_cpu(cpu_present_map);
if (!cpu_online(first_cpu)) {
error = _cpu_up(first_cpu);
if (error) {
printk(KERN_ERR "Could not bring CPU%d up.\n",
first_cpu);
goto out;
}
}
error = set_cpus_allowed(current, cpumask_of_cpu(first_cpu));
if (error) {
printk(KERN_ERR "Could not run on CPU%d\n", first_cpu);
goto out;
}
/* We take down all of the non-boot CPUs in one shot to avoid races
* with the userspace trying to use the CPU hotplug at the same time
*/
cpus_clear(frozen_cpus);
printk("Disabling non-boot CPUs ...\n");
for_each_online_cpu(cpu) {
if (cpu == first_cpu)
continue;
error = _cpu_down(cpu);
if (!error) {
cpu_set(cpu, frozen_cpus);
printk("CPU%d is down\n", cpu);
} else {
printk(KERN_ERR "Error taking CPU%d down: %d\n",
cpu, error);
break;
}
}
if (!error) {
BUG_ON(num_online_cpus() > 1);
/* Make sure the CPUs won't be enabled by someone else */
cpu_hotplug_disabled = 1;
} else {
printk(KERN_ERR "Non-boot CPUs are not disabled");
}
out:
mutex_unlock(&cpu_add_remove_lock);
return ret;
return error;
}

void enable_nonboot_cpus(void)
{
int cpu, error;

/* Allow everyone to use the CPU hotplug again */
mutex_lock(&cpu_add_remove_lock);
cpu_hotplug_disabled = 0;
mutex_unlock(&cpu_add_remove_lock);

printk("Enabling non-boot CPUs ...\n");
for_each_cpu_mask(cpu, frozen_cpus) {
error = cpu_up(cpu);
if (!error) {
printk("CPU%d is up\n", cpu);
continue;
}
printk(KERN_WARNING "Error taking CPU%d up: %d\n",
cpu, error);
}
cpus_clear(frozen_cpus);
}
#endif
2 changes: 0 additions & 2 deletions kernel/power/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,4 @@ obj-y := main.o process.o console.o
obj-$(CONFIG_PM_LEGACY) += pm.o
obj-$(CONFIG_SOFTWARE_SUSPEND) += swsusp.o disk.o snapshot.o swap.o user.o

obj-$(CONFIG_SUSPEND_SMP) += smp.o

obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
7 changes: 6 additions & 1 deletion kernel/power/disk.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/pm.h>
#include <linux/cpu.h>

#include "power.h"

Expand Down Expand Up @@ -72,7 +73,10 @@ static int prepare_processes(void)
int error;

pm_prepare_console();
disable_nonboot_cpus();

error = disable_nonboot_cpus();
if (error)
goto enable_cpus;

if (freeze_processes()) {
error = -EBUSY;
Expand All @@ -84,6 +88,7 @@ static int prepare_processes(void)
return 0;
thaw:
thaw_processes();
enable_cpus:
enable_nonboot_cpus();
pm_restore_console();
return error;
Expand Down
10 changes: 4 additions & 6 deletions kernel/power/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/console.h>
#include <linux/cpu.h>

#include "power.h"

Expand Down Expand Up @@ -51,20 +52,17 @@ void pm_set_ops(struct pm_ops * ops)

static int suspend_prepare(suspend_state_t state)
{
int error = 0;
int error;
unsigned int free_pages;

if (!pm_ops || !pm_ops->enter)
return -EPERM;

pm_prepare_console();

disable_nonboot_cpus();

if (num_online_cpus() != 1) {
error = -EPERM;
error = disable_nonboot_cpus();
if (error)
goto Enable_cpu;
}

if (freeze_processes()) {
error = -EAGAIN;
Expand Down
62 changes: 0 additions & 62 deletions kernel/power/smp.c

This file was deleted.

14 changes: 9 additions & 5 deletions kernel/power/user.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <linux/swapops.h>
#include <linux/pm.h>
#include <linux/fs.h>
#include <linux/cpu.h>

#include <asm/uaccess.h>

Expand Down Expand Up @@ -139,12 +140,15 @@ static int snapshot_ioctl(struct inode *inode, struct file *filp,
if (data->frozen)
break;
down(&pm_sem);
disable_nonboot_cpus();
if (freeze_processes()) {
thaw_processes();
enable_nonboot_cpus();
error = -EBUSY;
error = disable_nonboot_cpus();
if (!error) {
error = freeze_processes();
if (error) {
thaw_processes();
error = -EBUSY;
}
}
enable_nonboot_cpus();
up(&pm_sem);
if (!error)
data->frozen = 1;
Expand Down

0 comments on commit e3920fb

Please sign in to comment.