]> git.hungrycats.org Git - linux/commitdiff
[PATCH] stop_machine_run: Move Bogolock Code Out of module.c
authorRusty Russell <rusty@rustcorp.com.au>
Mon, 8 Mar 2004 09:12:45 +0000 (01:12 -0800)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 8 Mar 2004 09:12:45 +0000 (01:12 -0800)
The "bogolock" code was introduced in module.c, as a way of freezing
the machine when we wanted to remove a module.  This patch moves it
out to stop_machine.c and stop_machine.h.

Since the code changes affinity and proirity, it's impolite to hijack
the current context, so we use a kthread.  This means we have to pass
the function rather than implement "stop_machine()" and
"restart_machine()".

include/linux/stop_machine.h [new file with mode: 0644]
init/Kconfig
kernel/Makefile
kernel/stop_machine.c [new file with mode: 0644]

diff --git a/include/linux/stop_machine.h b/include/linux/stop_machine.h
new file mode 100644 (file)
index 0000000..6f43cb5
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef _LINUX_STOP_MACHINE
+#define _LINUX_STOP_MACHINE
+/* "Bogolock": stop the entire machine, disable interrupts.  This is a
+   very heavy lock, which is equivalent to grabbing every spinlock
+   (and more).  So the "read" side to such a lock is anything which
+   diables preeempt. */
+#include <linux/config.h>
+#include <linux/cpu.h>
+#include <asm/system.h>
+
+#ifdef CONFIG_SMP
+/**
+ * stop_machine_run: freeze the machine on all CPUs and run this function
+ * @fn: the function to run
+ * @data: the data ptr for the @fn()
+ * @cpu: the cpu to run @fn() on (or any, if @cpu == NR_CPUS.
+ *
+ * Description: This causes a thread to be scheduled on every other cpu,
+ * each of which disables interrupts, and finally interrupts are disabled
+ * on the current CPU.  The result is that noone is holding a spinlock
+ * or inside any other preempt-disabled region when @fn() runs.
+ *
+ * This can be thought of as a very heavy write lock, equivalent to
+ * grabbing every spinlock in the kernel. */
+int stop_machine_run(int (*fn)(void *), void *data, unsigned int cpu);
+
+/**
+ * __stop_machine_run: freeze the machine on all CPUs and run this function
+ * @fn: the function to run
+ * @data: the data ptr for the @fn
+ * @cpu: the cpu to run @fn on (or any, if @cpu == NR_CPUS.
+ *
+ * Description: This is a special version of the above, which returns the
+ * thread which has run @fn(): kthread_stop will return the return value
+ * of @fn().  Used by hotplug cpu.
+ */
+struct task_struct *__stop_machine_run(int (*fn)(void *), void *data,
+                                      unsigned int cpu);
+
+#else
+
+static inline int stop_machine_run(int (*fn)(void *), void *data,
+                                  unsigned int cpu)
+{
+       int ret;
+       local_irq_disable();
+       ret = fn(data);
+       local_irq_enable();
+       return ret;
+}
+#endif /* CONFIG_SMP */
+#endif /* _LINUX_STOP_MACHINE */
index 7c661d1bc27054adeb1bc15d2c8dea34dec99d4a..b8a7abc14cf0fc86a21a78309f3db14adcc83aab 100644 (file)
@@ -304,4 +304,10 @@ config KMOD
          runs modprobe with the appropriate arguments, thereby
          loading the module if it is available.  If unsure, say Y.
 
+config STOP_MACHINE
+       bool
+       default y
+       depends on (SMP && MODULE_UNLOAD) || HOTPLUG_CPU
+       help
+         Need stop_machine() primitive.
 endmenu
index 3a2651544349da29ef2f9f51b3cb37166326ffd5..3a6484838748fbee08de0cd0c37c27a857a057b9 100644 (file)
@@ -20,6 +20,7 @@ obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
 obj-$(CONFIG_COMPAT) += compat.o
 obj-$(CONFIG_IKCONFIG) += configs.o
 obj-$(CONFIG_IKCONFIG_PROC) += configs.o
+obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
 
 ifneq ($(CONFIG_IA64),y)
 # According to Alan Modra <alan@linuxcare.com.au>, the -fno-omit-frame-pointer is
diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c
new file mode 100644 (file)
index 0000000..ce7ca6b
--- /dev/null
@@ -0,0 +1,199 @@
+#include <linux/stop_machine.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/cpu.h>
+#include <linux/err.h>
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+
+/* Since we effect priority and affinity (both of which are visible
+ * to, and settable by outside processes) we do indirection via a
+ * kthread. */
+
+/* Thread to stop each CPU in user context. */
+enum stopmachine_state {
+       STOPMACHINE_WAIT,
+       STOPMACHINE_PREPARE,
+       STOPMACHINE_DISABLE_IRQ,
+       STOPMACHINE_EXIT,
+};
+
+static enum stopmachine_state stopmachine_state;
+static unsigned int stopmachine_num_threads;
+static atomic_t stopmachine_thread_ack;
+static DECLARE_MUTEX(stopmachine_mutex);
+
+static int stopmachine(void *cpu)
+{
+       int irqs_disabled = 0;
+       int prepared = 0;
+
+       set_cpus_allowed(current, cpumask_of_cpu((int)(long)cpu));
+
+       /* Ack: we are alive */
+       mb(); /* Theoretically the ack = 0 might not be on this CPU yet. */
+       atomic_inc(&stopmachine_thread_ack);
+
+       /* Simple state machine */
+       while (stopmachine_state != STOPMACHINE_EXIT) {
+               if (stopmachine_state == STOPMACHINE_DISABLE_IRQ 
+                   && !irqs_disabled) {
+                       local_irq_disable();
+                       irqs_disabled = 1;
+                       /* Ack: irqs disabled. */
+                       mb(); /* Must read state first. */
+                       atomic_inc(&stopmachine_thread_ack);
+               } else if (stopmachine_state == STOPMACHINE_PREPARE
+                          && !prepared) {
+                       /* Everyone is in place, hold CPU. */
+                       preempt_disable();
+                       prepared = 1;
+                       mb(); /* Must read state first. */
+                       atomic_inc(&stopmachine_thread_ack);
+               }
+               cpu_relax();
+       }
+
+       /* Ack: we are exiting. */
+       mb(); /* Must read state first. */
+       atomic_inc(&stopmachine_thread_ack);
+
+       if (irqs_disabled)
+               local_irq_enable();
+       if (prepared)
+               preempt_enable();
+
+       return 0;
+}
+
+/* Change the thread state */
+static void stopmachine_set_state(enum stopmachine_state state)
+{
+       atomic_set(&stopmachine_thread_ack, 0);
+       wmb();
+       stopmachine_state = state;
+       while (atomic_read(&stopmachine_thread_ack) != stopmachine_num_threads)
+               cpu_relax();
+}
+
+static int stop_machine(void)
+{
+       int i, ret = 0;
+       struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
+
+       /* One high-prio thread per cpu.  We'll do this one. */
+       sys_sched_setscheduler(current->pid, SCHED_FIFO, &param);
+
+       atomic_set(&stopmachine_thread_ack, 0);
+       stopmachine_num_threads = 0;
+       stopmachine_state = STOPMACHINE_WAIT;
+
+       for_each_online_cpu(i) {
+               if (i == smp_processor_id())
+                       continue;
+               ret = kernel_thread(stopmachine, (void *)(long)i,CLONE_KERNEL);
+               if (ret < 0)
+                       break;
+               stopmachine_num_threads++;
+       }
+
+       /* Wait for them all to come to life. */
+       while (atomic_read(&stopmachine_thread_ack) != stopmachine_num_threads)
+               yield();
+
+       /* If some failed, kill them all. */
+       if (ret < 0) {
+               stopmachine_set_state(STOPMACHINE_EXIT);
+               up(&stopmachine_mutex);
+               return ret;
+       }
+
+       /* Don't schedule us away at this point, please. */
+       local_irq_disable();
+
+       /* Now they are all started, make them hold the CPUs, ready. */
+       stopmachine_set_state(STOPMACHINE_PREPARE);
+
+       /* Make them disable irqs. */
+       stopmachine_set_state(STOPMACHINE_DISABLE_IRQ);
+
+       return 0;
+}
+
+static void restart_machine(void)
+{
+       stopmachine_set_state(STOPMACHINE_EXIT);
+       local_irq_enable();
+}
+
+struct stop_machine_data
+{
+       int (*fn)(void *);
+       void *data;
+       struct completion done;
+};
+
+static int do_stop(void *_smdata)
+{
+       struct stop_machine_data *smdata = _smdata;
+       int ret;
+
+       ret = stop_machine();
+       if (ret == 0) {
+               ret = smdata->fn(smdata->data);
+               restart_machine();
+       }
+
+       /* We're done: you can kthread_stop us now */
+       complete(&smdata->done);
+
+       /* Wait for kthread_stop */
+       while (!kthread_should_stop()) {
+               __set_current_state(TASK_INTERRUPTIBLE);
+               schedule();
+       }
+       return ret;
+}
+
+struct task_struct *__stop_machine_run(int (*fn)(void *), void *data,
+                                      unsigned int cpu)
+{
+       struct stop_machine_data smdata;
+       struct task_struct *p;
+
+       smdata.fn = fn;
+       smdata.data = data;
+       init_completion(&smdata.done);
+
+       down(&stopmachine_mutex);
+
+       /* If they don't care which CPU fn runs on, bind to any online one. */
+       if (cpu == NR_CPUS)
+               cpu = smp_processor_id();
+
+       p = kthread_create(do_stop, &smdata, "kstopmachine");
+       if (!IS_ERR(p)) {
+               kthread_bind(p, cpu);
+               wake_up_process(p);
+               wait_for_completion(&smdata.done);
+       }
+       up(&stopmachine_mutex);
+       return p;
+}
+
+int stop_machine_run(int (*fn)(void *), void *data, unsigned int cpu)
+{
+       struct task_struct *p;
+       int ret;
+
+       /* No CPUs can come up or down during this. */
+       lock_cpu_hotplug();
+       p = __stop_machine_run(fn, data, cpu);
+       if (!IS_ERR(p))
+               ret = kthread_stop(p);
+       else
+               ret = PTR_ERR(p);
+       unlock_cpu_hotplug();
+
+       return ret;
+}