diff options
Diffstat (limited to 'kernel/stop_machine.c')
-rw-r--r-- | kernel/stop_machine.c | 80 |
1 files changed, 74 insertions, 6 deletions
diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c index e3516b29076c..ba5070ce5765 100644 --- a/kernel/stop_machine.c +++ b/kernel/stop_machine.c | |||
@@ -19,7 +19,7 @@ | |||
19 | #include <linux/interrupt.h> | 19 | #include <linux/interrupt.h> |
20 | #include <linux/kallsyms.h> | 20 | #include <linux/kallsyms.h> |
21 | 21 | ||
22 | #include <asm/atomic.h> | 22 | #include <linux/atomic.h> |
23 | 23 | ||
24 | /* | 24 | /* |
25 | * Structure to determine completion condition and record errors. May | 25 | * Structure to determine completion condition and record errors. May |
@@ -136,10 +136,11 @@ void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, | |||
136 | static DEFINE_MUTEX(stop_cpus_mutex); | 136 | static DEFINE_MUTEX(stop_cpus_mutex); |
137 | static DEFINE_PER_CPU(struct cpu_stop_work, stop_cpus_work); | 137 | static DEFINE_PER_CPU(struct cpu_stop_work, stop_cpus_work); |
138 | 138 | ||
139 | int __stop_cpus(const struct cpumask *cpumask, cpu_stop_fn_t fn, void *arg) | 139 | static void queue_stop_cpus_work(const struct cpumask *cpumask, |
140 | cpu_stop_fn_t fn, void *arg, | ||
141 | struct cpu_stop_done *done) | ||
140 | { | 142 | { |
141 | struct cpu_stop_work *work; | 143 | struct cpu_stop_work *work; |
142 | struct cpu_stop_done done; | ||
143 | unsigned int cpu; | 144 | unsigned int cpu; |
144 | 145 | ||
145 | /* initialize works and done */ | 146 | /* initialize works and done */ |
@@ -147,9 +148,8 @@ int __stop_cpus(const struct cpumask *cpumask, cpu_stop_fn_t fn, void *arg) | |||
147 | work = &per_cpu(stop_cpus_work, cpu); | 148 | work = &per_cpu(stop_cpus_work, cpu); |
148 | work->fn = fn; | 149 | work->fn = fn; |
149 | work->arg = arg; | 150 | work->arg = arg; |
150 | work->done = &done; | 151 | work->done = done; |
151 | } | 152 | } |
152 | cpu_stop_init_done(&done, cpumask_weight(cpumask)); | ||
153 | 153 | ||
154 | /* | 154 | /* |
155 | * Disable preemption while queueing to avoid getting | 155 | * Disable preemption while queueing to avoid getting |
@@ -161,7 +161,15 @@ int __stop_cpus(const struct cpumask *cpumask, cpu_stop_fn_t fn, void *arg) | |||
161 | cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), | 161 | cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), |
162 | &per_cpu(stop_cpus_work, cpu)); | 162 | &per_cpu(stop_cpus_work, cpu)); |
163 | preempt_enable(); | 163 | preempt_enable(); |
164 | } | ||
164 | 165 | ||
166 | static int __stop_cpus(const struct cpumask *cpumask, | ||
167 | cpu_stop_fn_t fn, void *arg) | ||
168 | { | ||
169 | struct cpu_stop_done done; | ||
170 | |||
171 | cpu_stop_init_done(&done, cpumask_weight(cpumask)); | ||
172 | queue_stop_cpus_work(cpumask, fn, arg, &done); | ||
165 | wait_for_completion(&done.completion); | 173 | wait_for_completion(&done.completion); |
166 | return done.executed ? done.ret : -ENOENT; | 174 | return done.executed ? done.ret : -ENOENT; |
167 | } | 175 | } |
@@ -431,8 +439,15 @@ static int stop_machine_cpu_stop(void *data) | |||
431 | struct stop_machine_data *smdata = data; | 439 | struct stop_machine_data *smdata = data; |
432 | enum stopmachine_state curstate = STOPMACHINE_NONE; | 440 | enum stopmachine_state curstate = STOPMACHINE_NONE; |
433 | int cpu = smp_processor_id(), err = 0; | 441 | int cpu = smp_processor_id(), err = 0; |
442 | unsigned long flags; | ||
434 | bool is_active; | 443 | bool is_active; |
435 | 444 | ||
445 | /* | ||
446 | * When called from stop_machine_from_inactive_cpu(), irq might | ||
447 | * already be disabled. Save the state and restore it on exit. | ||
448 | */ | ||
449 | local_save_flags(flags); | ||
450 | |||
436 | if (!smdata->active_cpus) | 451 | if (!smdata->active_cpus) |
437 | is_active = cpu == cpumask_first(cpu_online_mask); | 452 | is_active = cpu == cpumask_first(cpu_online_mask); |
438 | else | 453 | else |
@@ -460,7 +475,7 @@ static int stop_machine_cpu_stop(void *data) | |||
460 | } | 475 | } |
461 | } while (curstate != STOPMACHINE_EXIT); | 476 | } while (curstate != STOPMACHINE_EXIT); |
462 | 477 | ||
463 | local_irq_enable(); | 478 | local_irq_restore(flags); |
464 | return err; | 479 | return err; |
465 | } | 480 | } |
466 | 481 | ||
@@ -487,4 +502,57 @@ int stop_machine(int (*fn)(void *), void *data, const struct cpumask *cpus) | |||
487 | } | 502 | } |
488 | EXPORT_SYMBOL_GPL(stop_machine); | 503 | EXPORT_SYMBOL_GPL(stop_machine); |
489 | 504 | ||
505 | /** | ||
506 | * stop_machine_from_inactive_cpu - stop_machine() from inactive CPU | ||
507 | * @fn: the function to run | ||
508 | * @data: the data ptr for the @fn() | ||
509 | * @cpus: the cpus to run the @fn() on (NULL = any online cpu) | ||
510 | * | ||
511 | * This is identical to stop_machine() but can be called from a CPU which | ||
512 | * is not active. The local CPU is in the process of hotplug (so no other | ||
513 | * CPU hotplug can start) and not marked active and doesn't have enough | ||
514 | * context to sleep. | ||
515 | * | ||
516 | * This function provides stop_machine() functionality for such state by | ||
517 | * using busy-wait for synchronization and executing @fn directly for local | ||
518 | * CPU. | ||
519 | * | ||
520 | * CONTEXT: | ||
521 | * Local CPU is inactive. Temporarily stops all active CPUs. | ||
522 | * | ||
523 | * RETURNS: | ||
524 | * 0 if all executions of @fn returned 0, any non zero return value if any | ||
525 | * returned non zero. | ||
526 | */ | ||
527 | int stop_machine_from_inactive_cpu(int (*fn)(void *), void *data, | ||
528 | const struct cpumask *cpus) | ||
529 | { | ||
530 | struct stop_machine_data smdata = { .fn = fn, .data = data, | ||
531 | .active_cpus = cpus }; | ||
532 | struct cpu_stop_done done; | ||
533 | int ret; | ||
534 | |||
535 | /* Local CPU must be inactive and CPU hotplug in progress. */ | ||
536 | BUG_ON(cpu_active(raw_smp_processor_id())); | ||
537 | smdata.num_threads = num_active_cpus() + 1; /* +1 for local */ | ||
538 | |||
539 | /* No proper task established and can't sleep - busy wait for lock. */ | ||
540 | while (!mutex_trylock(&stop_cpus_mutex)) | ||
541 | cpu_relax(); | ||
542 | |||
543 | /* Schedule work on other CPUs and execute directly for local CPU */ | ||
544 | set_state(&smdata, STOPMACHINE_PREPARE); | ||
545 | cpu_stop_init_done(&done, num_active_cpus()); | ||
546 | queue_stop_cpus_work(cpu_active_mask, stop_machine_cpu_stop, &smdata, | ||
547 | &done); | ||
548 | ret = stop_machine_cpu_stop(&smdata); | ||
549 | |||
550 | /* Busy wait for completion. */ | ||
551 | while (!completion_done(&done.completion)) | ||
552 | cpu_relax(); | ||
553 | |||
554 | mutex_unlock(&stop_cpus_mutex); | ||
555 | return ret ?: done.ret; | ||
556 | } | ||
557 | |||
490 | #endif /* CONFIG_STOP_MACHINE */ | 558 | #endif /* CONFIG_STOP_MACHINE */ |