stop_machine.c 4.5 KB
Newer Older
R
Rusty Russell 已提交
1
/* Copyright 2008, 2005 Rusty Russell rusty@rustcorp.com.au IBM Corporation.
R
Rusty Russell 已提交
2 3
 * GPL v2 and any later version.
 */
L
Linus Torvalds 已提交
4 5
#include <linux/cpu.h>
#include <linux/err.h>
6 7 8 9
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/stop_machine.h>
L
Linus Torvalds 已提交
10
#include <linux/syscalls.h>
11 12
#include <linux/interrupt.h>

L
Linus Torvalds 已提交
13 14 15
#include <asm/atomic.h>
#include <asm/uaccess.h>

R
Rusty Russell 已提交
16
/* This controls the threads on each CPU. */
L
Linus Torvalds 已提交
17
enum stopmachine_state {
R
Rusty Russell 已提交
18 19 20
	/* Dummy starting state for thread. */
	STOPMACHINE_NONE,
	/* Awaiting everyone to be scheduled. */
L
Linus Torvalds 已提交
21
	STOPMACHINE_PREPARE,
R
Rusty Russell 已提交
22
	/* Disable interrupts. */
L
Linus Torvalds 已提交
23
	STOPMACHINE_DISABLE_IRQ,
R
Rusty Russell 已提交
24
	/* Run the function */
J
Jason Baron 已提交
25
	STOPMACHINE_RUN,
R
Rusty Russell 已提交
26
	/* Exit */
L
Linus Torvalds 已提交
27 28
	STOPMACHINE_EXIT,
};
R
Rusty Russell 已提交
29
static enum stopmachine_state state;
L
Linus Torvalds 已提交
30

J
Jason Baron 已提交
31 32 33
struct stop_machine_data {
	int (*fn)(void *);
	void *data;
R
Rusty Russell 已提交
34 35
	int fnret;
};
J
Jason Baron 已提交
36

R
Rusty Russell 已提交
37 38 39 40
/* Like num_online_cpus(), but hotplug cpu uses us, so we need this. */
static unsigned int num_threads;
static atomic_t thread_ack;
static DEFINE_MUTEX(lock);
41 42 43 44
/* setup_lock protects refcount, stop_machine_wq and stop_machine_work. */
static DEFINE_MUTEX(setup_lock);
/* Users of stop_machine. */
static int refcount;
45 46 47 48 49
static struct workqueue_struct *stop_machine_wq;
static struct stop_machine_data active, idle;
static const cpumask_t *active_cpus;
static void *stop_machine_work;

R
Rusty Russell 已提交
50
static void set_state(enum stopmachine_state newstate)
L
Linus Torvalds 已提交
51
{
R
Rusty Russell 已提交
52 53 54 55
	/* Reset ack counter. */
	atomic_set(&thread_ack, num_threads);
	smp_wmb();
	state = newstate;
L
Linus Torvalds 已提交
56 57
}

R
Rusty Russell 已提交
58 59
/* Last one to ack a state moves to the next state. */
static void ack_state(void)
L
Linus Torvalds 已提交
60
{
61 62
	if (atomic_dec_and_test(&thread_ack))
		set_state(state + 1);
L
Linus Torvalds 已提交
63 64
}

65 66 67
/* This is the actual function which stops the CPU. It runs
 * in the context of a dedicated stopmachine workqueue. */
static void stop_cpu(struct work_struct *unused)
L
Linus Torvalds 已提交
68
{
R
Rusty Russell 已提交
69
	enum stopmachine_state curstate = STOPMACHINE_NONE;
70 71
	struct stop_machine_data *smdata = &idle;
	int cpu = smp_processor_id();
72
	int err;
73 74

	if (!active_cpus) {
75
		if (cpu == cpumask_first(cpu_online_mask))
76 77
			smdata = &active;
	} else {
78
		if (cpumask_test_cpu(cpu, active_cpus))
79 80
			smdata = &active;
	}
R
Rusty Russell 已提交
81 82 83
	/* Simple state machine */
	do {
		/* Chill out and ensure we re-read stopmachine_state. */
84
		cpu_relax();
R
Rusty Russell 已提交
85 86 87 88 89 90 91 92
		if (state != curstate) {
			curstate = state;
			switch (curstate) {
			case STOPMACHINE_DISABLE_IRQ:
				local_irq_disable();
				hard_irq_disable();
				break;
			case STOPMACHINE_RUN:
93 94 95 96 97
				/* On multiple CPUs only a single error code
				 * is needed to tell that something failed. */
				err = smdata->fn(smdata->data);
				if (err)
					smdata->fnret = err;
R
Rusty Russell 已提交
98 99 100 101 102 103 104
				break;
			default:
				break;
			}
			ack_state();
		}
	} while (curstate != STOPMACHINE_EXIT);
L
Linus Torvalds 已提交
105 106 107 108

	local_irq_enable();
}

R
Rusty Russell 已提交
109 110
/* Callback for CPUs which aren't supposed to do anything. */
static int chill(void *unused)
J
Jason Baron 已提交
111
{
R
Rusty Russell 已提交
112
	return 0;
J
Jason Baron 已提交
113
}
L
Linus Torvalds 已提交
114

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
int stop_machine_create(void)
{
	mutex_lock(&setup_lock);
	if (refcount)
		goto done;
	stop_machine_wq = create_rt_workqueue("kstop");
	if (!stop_machine_wq)
		goto err_out;
	stop_machine_work = alloc_percpu(struct work_struct);
	if (!stop_machine_work)
		goto err_out;
done:
	refcount++;
	mutex_unlock(&setup_lock);
	return 0;

err_out:
	if (stop_machine_wq)
		destroy_workqueue(stop_machine_wq);
	mutex_unlock(&setup_lock);
	return -ENOMEM;
}
EXPORT_SYMBOL_GPL(stop_machine_create);

void stop_machine_destroy(void)
{
	mutex_lock(&setup_lock);
	refcount--;
	if (refcount)
		goto done;
	destroy_workqueue(stop_machine_wq);
	free_percpu(stop_machine_work);
done:
	mutex_unlock(&setup_lock);
}
EXPORT_SYMBOL_GPL(stop_machine_destroy);

152
int __stop_machine(int (*fn)(void *), void *data, const struct cpumask *cpus)
L
Linus Torvalds 已提交
153
{
154
	struct work_struct *sm_work;
155
	int i, ret;
R
Rusty Russell 已提交
156

157 158 159 160
	/* Set up initial state. */
	mutex_lock(&lock);
	num_threads = num_online_cpus();
	active_cpus = cpus;
R
Rusty Russell 已提交
161 162 163 164 165 166 167
	active.fn = fn;
	active.data = data;
	active.fnret = 0;
	idle.fn = chill;
	idle.data = NULL;

	set_state(STOPMACHINE_PREPARE);
L
Linus Torvalds 已提交
168

169
	/* Schedule the stop_cpu work on all cpus: hold this CPU so one
R
Rusty Russell 已提交
170
	 * doesn't hit this CPU until we're ready. */
171
	get_cpu();
172 173 174 175 176
	for_each_online_cpu(i) {
		sm_work = percpu_ptr(stop_machine_work, i);
		INIT_WORK(sm_work, stop_cpu);
		queue_work_on(i, stop_machine_wq, sm_work);
	}
R
Rusty Russell 已提交
177 178
	/* This will release the thread on our CPU. */
	put_cpu();
179
	flush_workqueue(stop_machine_wq);
180
	ret = active.fnret;
R
Rusty Russell 已提交
181
	mutex_unlock(&lock);
182
	return ret;
L
Linus Torvalds 已提交
183 184
}

185
int stop_machine(int (*fn)(void *), void *data, const struct cpumask *cpus)
L
Linus Torvalds 已提交
186 187 188
{
	int ret;

189 190 191
	ret = stop_machine_create();
	if (ret)
		return ret;
L
Linus Torvalds 已提交
192
	/* No CPUs can come up or down during this. */
193
	get_online_cpus();
194
	ret = __stop_machine(fn, data, cpus);
195
	put_online_cpus();
196
	stop_machine_destroy();
L
Linus Torvalds 已提交
197 198
	return ret;
}
199
EXPORT_SYMBOL_GPL(stop_machine);