diff options
| -rw-r--r-- | include/linux/smpboot.h | 43 | ||||
| -rw-r--r-- | kernel/cpu.c | 10 | ||||
| -rw-r--r-- | kernel/smpboot.c | 229 | ||||
| -rw-r--r-- | kernel/smpboot.h | 4 |
4 files changed, 285 insertions, 1 deletions
diff --git a/include/linux/smpboot.h b/include/linux/smpboot.h new file mode 100644 index 000000000000..e0106d8581d3 --- /dev/null +++ b/include/linux/smpboot.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | #ifndef _LINUX_SMPBOOT_H | ||
| 2 | #define _LINUX_SMPBOOT_H | ||
| 3 | |||
| 4 | #include <linux/types.h> | ||
| 5 | |||
| 6 | struct task_struct; | ||
| 7 | /* Cookie handed to the thread_fn*/ | ||
| 8 | struct smpboot_thread_data; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * struct smp_hotplug_thread - CPU hotplug related thread descriptor | ||
| 12 | * @store: Pointer to per cpu storage for the task pointers | ||
| 13 | * @list: List head for core management | ||
| 14 | * @thread_should_run: Check whether the thread should run or not. Called with | ||
| 15 | * preemption disabled. | ||
| 16 | * @thread_fn: The associated thread function | ||
| 17 | * @setup: Optional setup function, called when the thread gets | ||
| 18 | * operational the first time | ||
| 19 | * @cleanup: Optional cleanup function, called when the thread | ||
| 20 | * should stop (module exit) | ||
| 21 | * @park: Optional park function, called when the thread is | ||
| 22 | * parked (cpu offline) | ||
| 23 | * @unpark: Optional unpark function, called when the thread is | ||
| 24 | * unparked (cpu online) | ||
| 25 | * @thread_comm: The base name of the thread | ||
| 26 | */ | ||
| 27 | struct smp_hotplug_thread { | ||
| 28 | struct task_struct __percpu **store; | ||
| 29 | struct list_head list; | ||
| 30 | int (*thread_should_run)(unsigned int cpu); | ||
| 31 | void (*thread_fn)(unsigned int cpu); | ||
| 32 | void (*setup)(unsigned int cpu); | ||
| 33 | void (*cleanup)(unsigned int cpu, bool online); | ||
| 34 | void (*park)(unsigned int cpu); | ||
| 35 | void (*unpark)(unsigned int cpu); | ||
| 36 | const char *thread_comm; | ||
| 37 | }; | ||
| 38 | |||
| 39 | int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread); | ||
| 40 | void smpboot_unregister_percpu_thread(struct smp_hotplug_thread *plug_thread); | ||
| 41 | int smpboot_thread_schedule(void); | ||
| 42 | |||
| 43 | #endif | ||
diff --git a/kernel/cpu.c b/kernel/cpu.c index 14d32588cccd..e615dfbcf794 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c | |||
| @@ -280,12 +280,13 @@ static int __ref _cpu_down(unsigned int cpu, int tasks_frozen) | |||
| 280 | __func__, cpu); | 280 | __func__, cpu); |
| 281 | goto out_release; | 281 | goto out_release; |
| 282 | } | 282 | } |
| 283 | smpboot_park_threads(cpu); | ||
| 283 | 284 | ||
| 284 | err = __stop_machine(take_cpu_down, &tcd_param, cpumask_of(cpu)); | 285 | err = __stop_machine(take_cpu_down, &tcd_param, cpumask_of(cpu)); |
| 285 | if (err) { | 286 | if (err) { |
| 286 | /* CPU didn't die: tell everyone. Can't complain. */ | 287 | /* CPU didn't die: tell everyone. Can't complain. */ |
| 288 | smpboot_unpark_threads(cpu); | ||
| 287 | cpu_notify_nofail(CPU_DOWN_FAILED | mod, hcpu); | 289 | cpu_notify_nofail(CPU_DOWN_FAILED | mod, hcpu); |
| 288 | |||
| 289 | goto out_release; | 290 | goto out_release; |
| 290 | } | 291 | } |
| 291 | BUG_ON(cpu_online(cpu)); | 292 | BUG_ON(cpu_online(cpu)); |
| @@ -354,6 +355,10 @@ static int __cpuinit _cpu_up(unsigned int cpu, int tasks_frozen) | |||
| 354 | goto out; | 355 | goto out; |
| 355 | } | 356 | } |
| 356 | 357 | ||
| 358 | ret = smpboot_create_threads(cpu); | ||
| 359 | if (ret) | ||
| 360 | goto out; | ||
| 361 | |||
| 357 | ret = __cpu_notify(CPU_UP_PREPARE | mod, hcpu, -1, &nr_calls); | 362 | ret = __cpu_notify(CPU_UP_PREPARE | mod, hcpu, -1, &nr_calls); |
| 358 | if (ret) { | 363 | if (ret) { |
| 359 | nr_calls--; | 364 | nr_calls--; |
| @@ -368,6 +373,9 @@ static int __cpuinit _cpu_up(unsigned int cpu, int tasks_frozen) | |||
| 368 | goto out_notify; | 373 | goto out_notify; |
| 369 | BUG_ON(!cpu_online(cpu)); | 374 | BUG_ON(!cpu_online(cpu)); |
| 370 | 375 | ||
| 376 | /* Wake the per cpu threads */ | ||
| 377 | smpboot_unpark_threads(cpu); | ||
| 378 | |||
| 371 | /* Now call notifier in preparation. */ | 379 | /* Now call notifier in preparation. */ |
| 372 | cpu_notify(CPU_ONLINE | mod, hcpu); | 380 | cpu_notify(CPU_ONLINE | mod, hcpu); |
| 373 | 381 | ||
diff --git a/kernel/smpboot.c b/kernel/smpboot.c index 98f60c5caa1b..9d5f7b04025d 100644 --- a/kernel/smpboot.c +++ b/kernel/smpboot.c | |||
| @@ -1,11 +1,17 @@ | |||
| 1 | /* | 1 | /* |
| 2 | * Common SMP CPU bringup/teardown functions | 2 | * Common SMP CPU bringup/teardown functions |
| 3 | */ | 3 | */ |
| 4 | #include <linux/cpu.h> | ||
| 4 | #include <linux/err.h> | 5 | #include <linux/err.h> |
| 5 | #include <linux/smp.h> | 6 | #include <linux/smp.h> |
| 6 | #include <linux/init.h> | 7 | #include <linux/init.h> |
| 8 | #include <linux/list.h> | ||
| 9 | #include <linux/slab.h> | ||
| 7 | #include <linux/sched.h> | 10 | #include <linux/sched.h> |
| 11 | #include <linux/export.h> | ||
| 8 | #include <linux/percpu.h> | 12 | #include <linux/percpu.h> |
| 13 | #include <linux/kthread.h> | ||
| 14 | #include <linux/smpboot.h> | ||
| 9 | 15 | ||
| 10 | #include "smpboot.h" | 16 | #include "smpboot.h" |
| 11 | 17 | ||
| @@ -65,3 +71,226 @@ void __init idle_threads_init(void) | |||
| 65 | } | 71 | } |
| 66 | } | 72 | } |
| 67 | #endif | 73 | #endif |
| 74 | |||
| 75 | static LIST_HEAD(hotplug_threads); | ||
| 76 | static DEFINE_MUTEX(smpboot_threads_lock); | ||
| 77 | |||
| 78 | struct smpboot_thread_data { | ||
| 79 | unsigned int cpu; | ||
| 80 | unsigned int status; | ||
| 81 | struct smp_hotplug_thread *ht; | ||
| 82 | }; | ||
| 83 | |||
| 84 | enum { | ||
| 85 | HP_THREAD_NONE = 0, | ||
| 86 | HP_THREAD_ACTIVE, | ||
| 87 | HP_THREAD_PARKED, | ||
| 88 | }; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * smpboot_thread_fn - percpu hotplug thread loop function | ||
| 92 | * @data: thread data pointer | ||
| 93 | * | ||
| 94 | * Checks for thread stop and park conditions. Calls the necessary | ||
| 95 | * setup, cleanup, park and unpark functions for the registered | ||
| 96 | * thread. | ||
| 97 | * | ||
| 98 | * Returns 1 when the thread should exit, 0 otherwise. | ||
| 99 | */ | ||
| 100 | static int smpboot_thread_fn(void *data) | ||
| 101 | { | ||
| 102 | struct smpboot_thread_data *td = data; | ||
| 103 | struct smp_hotplug_thread *ht = td->ht; | ||
| 104 | |||
| 105 | while (1) { | ||
| 106 | set_current_state(TASK_INTERRUPTIBLE); | ||
| 107 | preempt_disable(); | ||
| 108 | if (kthread_should_stop()) { | ||
| 109 | set_current_state(TASK_RUNNING); | ||
| 110 | preempt_enable(); | ||
| 111 | if (ht->cleanup) | ||
| 112 | ht->cleanup(td->cpu, cpu_online(td->cpu)); | ||
| 113 | kfree(td); | ||
| 114 | return 0; | ||
| 115 | } | ||
| 116 | |||
| 117 | if (kthread_should_park()) { | ||
| 118 | __set_current_state(TASK_RUNNING); | ||
| 119 | preempt_enable(); | ||
| 120 | if (ht->park && td->status == HP_THREAD_ACTIVE) { | ||
| 121 | BUG_ON(td->cpu != smp_processor_id()); | ||
| 122 | ht->park(td->cpu); | ||
| 123 | td->status = HP_THREAD_PARKED; | ||
| 124 | } | ||
| 125 | kthread_parkme(); | ||
| 126 | /* We might have been woken for stop */ | ||
| 127 | continue; | ||
| 128 | } | ||
| 129 | |||
| 130 | BUG_ON(td->cpu != smp_processor_id()); | ||
| 131 | |||
| 132 | /* Check for state change setup */ | ||
| 133 | switch (td->status) { | ||
| 134 | case HP_THREAD_NONE: | ||
| 135 | preempt_enable(); | ||
| 136 | if (ht->setup) | ||
| 137 | ht->setup(td->cpu); | ||
| 138 | td->status = HP_THREAD_ACTIVE; | ||
| 139 | preempt_disable(); | ||
| 140 | break; | ||
| 141 | case HP_THREAD_PARKED: | ||
| 142 | preempt_enable(); | ||
| 143 | if (ht->unpark) | ||
| 144 | ht->unpark(td->cpu); | ||
| 145 | td->status = HP_THREAD_ACTIVE; | ||
| 146 | preempt_disable(); | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | |||
| 150 | if (!ht->thread_should_run(td->cpu)) { | ||
| 151 | preempt_enable(); | ||
| 152 | schedule(); | ||
| 153 | } else { | ||
| 154 | set_current_state(TASK_RUNNING); | ||
| 155 | preempt_enable(); | ||
| 156 | ht->thread_fn(td->cpu); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | static int | ||
| 162 | __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu) | ||
| 163 | { | ||
| 164 | struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu); | ||
| 165 | struct smpboot_thread_data *td; | ||
| 166 | |||
| 167 | if (tsk) | ||
| 168 | return 0; | ||
| 169 | |||
| 170 | td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu)); | ||
| 171 | if (!td) | ||
| 172 | return -ENOMEM; | ||
| 173 | td->cpu = cpu; | ||
| 174 | td->ht = ht; | ||
| 175 | |||
| 176 | tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu, | ||
| 177 | ht->thread_comm); | ||
| 178 | if (IS_ERR(tsk)) { | ||
| 179 | kfree(td); | ||
| 180 | return PTR_ERR(tsk); | ||
| 181 | } | ||
| 182 | |||
| 183 | get_task_struct(tsk); | ||
| 184 | *per_cpu_ptr(ht->store, cpu) = tsk; | ||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | int smpboot_create_threads(unsigned int cpu) | ||
| 189 | { | ||
| 190 | struct smp_hotplug_thread *cur; | ||
| 191 | int ret = 0; | ||
| 192 | |||
| 193 | mutex_lock(&smpboot_threads_lock); | ||
| 194 | list_for_each_entry(cur, &hotplug_threads, list) { | ||
| 195 | ret = __smpboot_create_thread(cur, cpu); | ||
| 196 | if (ret) | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | mutex_unlock(&smpboot_threads_lock); | ||
| 200 | return ret; | ||
| 201 | } | ||
| 202 | |||
| 203 | static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cpu) | ||
| 204 | { | ||
| 205 | struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu); | ||
| 206 | |||
| 207 | kthread_unpark(tsk); | ||
| 208 | } | ||
| 209 | |||
| 210 | void smpboot_unpark_threads(unsigned int cpu) | ||
| 211 | { | ||
| 212 | struct smp_hotplug_thread *cur; | ||
| 213 | |||
| 214 | mutex_lock(&smpboot_threads_lock); | ||
| 215 | list_for_each_entry(cur, &hotplug_threads, list) | ||
| 216 | smpboot_unpark_thread(cur, cpu); | ||
| 217 | mutex_unlock(&smpboot_threads_lock); | ||
| 218 | } | ||
| 219 | |||
| 220 | static void smpboot_park_thread(struct smp_hotplug_thread *ht, unsigned int cpu) | ||
| 221 | { | ||
| 222 | struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu); | ||
| 223 | |||
| 224 | if (tsk) | ||
| 225 | kthread_park(tsk); | ||
| 226 | } | ||
| 227 | |||
| 228 | void smpboot_park_threads(unsigned int cpu) | ||
| 229 | { | ||
| 230 | struct smp_hotplug_thread *cur; | ||
| 231 | |||
| 232 | mutex_lock(&smpboot_threads_lock); | ||
| 233 | list_for_each_entry_reverse(cur, &hotplug_threads, list) | ||
| 234 | smpboot_park_thread(cur, cpu); | ||
| 235 | mutex_unlock(&smpboot_threads_lock); | ||
| 236 | } | ||
| 237 | |||
| 238 | static void smpboot_destroy_threads(struct smp_hotplug_thread *ht) | ||
| 239 | { | ||
| 240 | unsigned int cpu; | ||
| 241 | |||
| 242 | /* We need to destroy also the parked threads of offline cpus */ | ||
| 243 | for_each_possible_cpu(cpu) { | ||
| 244 | struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu); | ||
| 245 | |||
| 246 | if (tsk) { | ||
| 247 | kthread_stop(tsk); | ||
| 248 | put_task_struct(tsk); | ||
| 249 | *per_cpu_ptr(ht->store, cpu) = NULL; | ||
| 250 | } | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | /** | ||
| 255 | * smpboot_register_percpu_thread - Register a per_cpu thread related to hotplug | ||
| 256 | * @plug_thread: Hotplug thread descriptor | ||
| 257 | * | ||
| 258 | * Creates and starts the threads on all online cpus. | ||
| 259 | */ | ||
| 260 | int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread) | ||
| 261 | { | ||
| 262 | unsigned int cpu; | ||
| 263 | int ret = 0; | ||
| 264 | |||
| 265 | mutex_lock(&smpboot_threads_lock); | ||
| 266 | for_each_online_cpu(cpu) { | ||
| 267 | ret = __smpboot_create_thread(plug_thread, cpu); | ||
| 268 | if (ret) { | ||
| 269 | smpboot_destroy_threads(plug_thread); | ||
| 270 | goto out; | ||
| 271 | } | ||
| 272 | smpboot_unpark_thread(plug_thread, cpu); | ||
| 273 | } | ||
| 274 | list_add(&plug_thread->list, &hotplug_threads); | ||
| 275 | out: | ||
| 276 | mutex_unlock(&smpboot_threads_lock); | ||
| 277 | return ret; | ||
| 278 | } | ||
| 279 | EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread); | ||
| 280 | |||
| 281 | /** | ||
| 282 | * smpboot_unregister_percpu_thread - Unregister a per_cpu thread related to hotplug | ||
| 283 | * @plug_thread: Hotplug thread descriptor | ||
| 284 | * | ||
| 285 | * Stops all threads on all possible cpus. | ||
| 286 | */ | ||
| 287 | void smpboot_unregister_percpu_thread(struct smp_hotplug_thread *plug_thread) | ||
| 288 | { | ||
| 289 | get_online_cpus(); | ||
| 290 | mutex_lock(&smpboot_threads_lock); | ||
| 291 | list_del(&plug_thread->list); | ||
| 292 | smpboot_destroy_threads(plug_thread); | ||
| 293 | mutex_unlock(&smpboot_threads_lock); | ||
| 294 | put_online_cpus(); | ||
| 295 | } | ||
| 296 | EXPORT_SYMBOL_GPL(smpboot_unregister_percpu_thread); | ||
diff --git a/kernel/smpboot.h b/kernel/smpboot.h index 6ef9433e1c70..72415a0eb955 100644 --- a/kernel/smpboot.h +++ b/kernel/smpboot.h | |||
| @@ -13,4 +13,8 @@ static inline void idle_thread_set_boot_cpu(void) { } | |||
| 13 | static inline void idle_threads_init(void) { } | 13 | static inline void idle_threads_init(void) { } |
| 14 | #endif | 14 | #endif |
| 15 | 15 | ||
| 16 | int smpboot_create_threads(unsigned int cpu); | ||
| 17 | void smpboot_park_threads(unsigned int cpu); | ||
| 18 | void smpboot_unpark_threads(unsigned int cpu); | ||
| 19 | |||
| 16 | #endif | 20 | #endif |
