stop_machine.c 4.3 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 41
/* Like num_online_cpus(), but hotplug cpu uses us, so we need this. */
static unsigned int num_threads;
static atomic_t thread_ack;
static struct completion finished;
static DEFINE_MUTEX(lock);
L
Linus Torvalds 已提交
42

R
Rusty Russell 已提交
43
static void set_state(enum stopmachine_state newstate)
L
Linus Torvalds 已提交
44
{
R
Rusty Russell 已提交
45 46 47 48
	/* Reset ack counter. */
	atomic_set(&thread_ack, num_threads);
	smp_wmb();
	state = newstate;
L
Linus Torvalds 已提交
49 50
}

R
Rusty Russell 已提交
51 52
/* Last one to ack a state moves to the next state. */
static void ack_state(void)
L
Linus Torvalds 已提交
53
{
R
Rusty Russell 已提交
54 55 56 57 58 59 60
	if (atomic_dec_and_test(&thread_ack)) {
		/* If we're the last one to ack the EXIT, we're finished. */
		if (state == STOPMACHINE_EXIT)
			complete(&finished);
		else
			set_state(state + 1);
	}
L
Linus Torvalds 已提交
61 62
}

R
Rusty Russell 已提交
63 64 65
/* This is the actual thread which stops the CPU.  It exits by itself rather
 * than waiting for kthread_stop(), because it's easier for hotplug CPU. */
static int stop_cpu(struct stop_machine_data *smdata)
L
Linus Torvalds 已提交
66
{
R
Rusty Russell 已提交
67 68
	enum stopmachine_state curstate = STOPMACHINE_NONE;
	int uninitialized_var(ret);
L
Linus Torvalds 已提交
69

R
Rusty Russell 已提交
70 71 72
	/* Simple state machine */
	do {
		/* Chill out and ensure we re-read stopmachine_state. */
73
		cpu_relax();
R
Rusty Russell 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
		if (state != curstate) {
			curstate = state;
			switch (curstate) {
			case STOPMACHINE_DISABLE_IRQ:
				local_irq_disable();
				hard_irq_disable();
				break;
			case STOPMACHINE_RUN:
				/* |= allows error detection if functions on
				 * multiple CPUs. */
				smdata->fnret |= smdata->fn(smdata->data);
				break;
			default:
				break;
			}
			ack_state();
		}
	} while (curstate != STOPMACHINE_EXIT);
L
Linus Torvalds 已提交
92 93

	local_irq_enable();
R
Rusty Russell 已提交
94
	do_exit(0);
L
Linus Torvalds 已提交
95 96
}

R
Rusty Russell 已提交
97 98
/* Callback for CPUs which aren't supposed to do anything. */
static int chill(void *unused)
J
Jason Baron 已提交
99
{
R
Rusty Russell 已提交
100
	return 0;
J
Jason Baron 已提交
101
}
L
Linus Torvalds 已提交
102

R
Rusty Russell 已提交
103
int __stop_machine_run(int (*fn)(void *), void *data, unsigned int cpu)
L
Linus Torvalds 已提交
104
{
R
Rusty Russell 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
	int i, err;
	struct stop_machine_data active, idle;
	struct task_struct **threads;

	active.fn = fn;
	active.data = data;
	active.fnret = 0;
	idle.fn = chill;
	idle.data = NULL;

	/* If they don't care which cpu fn runs on, just pick one. */
	if (cpu == NR_CPUS)
		cpu = any_online_cpu(cpu_online_map);

	/* This could be too big for stack on large machines. */
	threads = kcalloc(NR_CPUS, sizeof(threads[0]), GFP_KERNEL);
	if (!threads)
		return -ENOMEM;

	/* Set up initial state. */
	mutex_lock(&lock);
	init_completion(&finished);
	num_threads = num_online_cpus();
	set_state(STOPMACHINE_PREPARE);
L
Linus Torvalds 已提交
129

R
Rusty Russell 已提交
130 131 132
	for_each_online_cpu(i) {
		struct stop_machine_data *smdata;
		struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
L
Linus Torvalds 已提交
133

R
Rusty Russell 已提交
134 135 136 137 138 139 140 141 142 143 144 145
		if (cpu == ALL_CPUS || i == cpu)
			smdata = &active;
		else
			smdata = &idle;

		threads[i] = kthread_create((void *)stop_cpu, smdata, "kstop%u",
					    i);
		if (IS_ERR(threads[i])) {
			err = PTR_ERR(threads[i]);
			threads[i] = NULL;
			goto kill_threads;
		}
L
Linus Torvalds 已提交
146

R
Rusty Russell 已提交
147 148
		/* Place it onto correct cpu. */
		kthread_bind(threads[i], i);
L
Linus Torvalds 已提交
149

R
Rusty Russell 已提交
150 151 152 153
		/* Make it highest prio. */
		if (sched_setscheduler_nocheck(threads[i], SCHED_FIFO, &param))
			BUG();
	}
L
Linus Torvalds 已提交
154

R
Rusty Russell 已提交
155 156 157 158 159
	/* We've created all the threads.  Wake them all: hold this CPU so one
	 * doesn't hit this CPU until we're ready. */
	cpu = get_cpu();
	for_each_online_cpu(i)
		wake_up_process(threads[i]);
J
Jason Baron 已提交
160

R
Rusty Russell 已提交
161 162 163 164
	/* This will release the thread on our CPU. */
	put_cpu();
	wait_for_completion(&finished);
	mutex_unlock(&lock);
L
Linus Torvalds 已提交
165

R
Rusty Russell 已提交
166
	kfree(threads);
L
Linus Torvalds 已提交
167

R
Rusty Russell 已提交
168
	return active.fnret;
L
Linus Torvalds 已提交
169

R
Rusty Russell 已提交
170 171 172 173 174
kill_threads:
	for_each_online_cpu(i)
		if (threads[i])
			kthread_stop(threads[i]);
	mutex_unlock(&lock);
175

R
Rusty Russell 已提交
176 177
	kfree(threads);
	return err;
L
Linus Torvalds 已提交
178 179 180 181 182 183 184
}

int stop_machine_run(int (*fn)(void *), void *data, unsigned int cpu)
{
	int ret;

	/* No CPUs can come up or down during this. */
185
	get_online_cpus();
R
Rusty Russell 已提交
186
	ret = __stop_machine_run(fn, data, cpu);
187
	put_online_cpus();
L
Linus Torvalds 已提交
188 189 190

	return ret;
}
191
EXPORT_SYMBOL_GPL(stop_machine_run);