diff options
author | Oleg Nesterov <oleg@redhat.com> | 2014-01-29 16:51:42 +1100 |
---|---|---|
committer | Eli Qiao <taget@linux.vnet.ibm.com> | 2014-01-29 15:34:28 +0800 |
commit | 2775d623072188316e8b42cca7ac2e8ba0332edf (patch) | |
tree | c645f7fe22d584571274d26a75fc5ac03d443a36 | |
parent | 76d38fe1553889aee5caba523178edc4d6d0f06e (diff) | |
download | powerkvm-2775d623072188316e8b42cca7ac2e8ba0332edf.tar.gz |
md: Avoid deadlock in raid5_alloc_percpu
register_cpu_notifier() can deadlock if called inside a
get/put_online_cpus block. To avoid this, move the call to
register_cpu_notifier before the get_online_cpus().
[paulus@samba.org - renamed alloc_xxx to alloc_percpu_areas, fixed
compile errors, made up patch description]
This fixes BZ 103213.
Signed-off-by: Paul Mackerras <paulus@samba.org>
-rw-r--r-- | drivers/md/raid5.c | 73 |
1 files changed, 34 insertions, 39 deletions
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 4bed5454b8dc1..2091f31ba80e9 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -5067,6 +5067,25 @@ static void free_conf(struct r5conf *conf) kfree(conf); } +static int alloc_percpu_areas(struct r5conf *conf, struct raid5_percpu *percpu, + int cpu) +{ + if (conf->level == 6 && !percpu->spare_page) + percpu->spare_page = alloc_page(GFP_KERNEL); + if (!percpu->scribble) + percpu->scribble = kmalloc(conf->scribble_len, GFP_KERNEL); + + if (!percpu->scribble || (conf->level == 6 && !percpu->spare_page)) { + safe_put_page(percpu->spare_page); + kfree(percpu->scribble); + pr_err("%s: failed memory allocation for cpu%d\n", + __func__, cpu); + return -ENOMEM; + } + + return 0; +} + #ifdef CONFIG_HOTPLUG_CPU static int raid456_cpu_notify(struct notifier_block *nfb, unsigned long action, void *hcpu) @@ -5078,19 +5097,8 @@ static int raid456_cpu_notify(struct notifier_block *nfb, unsigned long action, switch (action) { case CPU_UP_PREPARE: case CPU_UP_PREPARE_FROZEN: - if (conf->level == 6 && !percpu->spare_page) - percpu->spare_page = alloc_page(GFP_KERNEL); - if (!percpu->scribble) - percpu->scribble = kmalloc(conf->scribble_len, GFP_KERNEL); - - if (!percpu->scribble || - (conf->level == 6 && !percpu->spare_page)) { - safe_put_page(percpu->spare_page); - kfree(percpu->scribble); - pr_err("%s: failed memory allocation for cpu%ld\n", - __func__, cpu); + if (alloc_percpu_areas(conf, percpu, cpu)) return notifier_from_errno(-ENOMEM); - } break; case CPU_DEAD: case CPU_DEAD_FROZEN: @@ -5109,40 +5117,27 @@ static int raid456_cpu_notify(struct notifier_block *nfb, unsigned long action, static int raid5_alloc_percpu(struct r5conf *conf) { unsigned long cpu; - struct page *spare_page; - struct raid5_percpu __percpu *allcpus; - void *scribble; - int err; + int err = 0; - allcpus = alloc_percpu(struct raid5_percpu); - if (!allcpus) + conf->percpu = alloc_percpu(struct raid5_percpu); + if (!conf->percpu) return -ENOMEM; - conf->percpu = allcpus; - get_online_cpus(); - err = 0; - for_each_present_cpu(cpu) { - if (conf->level == 6) { - spare_page = alloc_page(GFP_KERNEL); - if (!spare_page) { - err = -ENOMEM; - break; - } - per_cpu_ptr(conf->percpu, cpu)->spare_page = spare_page; - } - scribble = kmalloc(conf->scribble_len, GFP_KERNEL); - if (!scribble) { - err = -ENOMEM; - break; - } - per_cpu_ptr(conf->percpu, cpu)->scribble = scribble; - } #ifdef CONFIG_HOTPLUG_CPU conf->cpu_notify.notifier_call = raid456_cpu_notify; conf->cpu_notify.priority = 0; - if (err == 0) - err = register_cpu_notifier(&conf->cpu_notify); + err = register_cpu_notifier(&conf->cpu_notify); + if (err) + return err; #endif + + get_online_cpus(); + for_each_present_cpu(cpu) { + err = alloc_percpu_areas(conf, per_cpu_ptr(conf->percpu, cpu), + cpu); + if (err) + break; + } put_online_cpus(); return err; |