diff --git a/xen/arch/x86/cpu/mcheck/mce.c b/xen/arch/x86/cpu/mcheck/mce.c index a8c287d124..32273d9208 100644 --- a/xen/arch/x86/cpu/mcheck/mce.c +++ b/xen/arch/x86/cpu/mcheck/mce.c @@ -692,12 +692,15 @@ static void cpu_bank_free(unsigned int cpu) mcabanks_free(poll); mcabanks_free(clr); + + per_cpu(poll_bankmask, cpu) = NULL; + per_cpu(mce_clear_banks, cpu) = NULL; } static int cpu_bank_alloc(unsigned int cpu) { - struct mca_banks *poll = mcabanks_alloc(); - struct mca_banks *clr = mcabanks_alloc(); + struct mca_banks *poll = per_cpu(poll_bankmask, cpu) ?: mcabanks_alloc(); + struct mca_banks *clr = per_cpu(mce_clear_banks, cpu) ?: mcabanks_alloc(); if ( !poll || !clr ) { @@ -725,7 +728,13 @@ static int cpu_callback( case CPU_UP_CANCELED: case CPU_DEAD: - cpu_bank_free(cpu); + if ( !park_offline_cpus ) + cpu_bank_free(cpu); + break; + + case CPU_REMOVE: + if ( park_offline_cpus ) + cpu_bank_free(cpu); break; } diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index 8b160d34b5..b5bb0f3b22 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -107,10 +107,11 @@ static void play_dead(void) local_irq_disable(); /* - * NOTE: After cpu_exit_clear, per-cpu variables are no longer accessible, - * as they may be freed at any time. In this case, heap corruption or - * #PF can occur (when heap debugging is enabled). For example, even - * printk() can involve tasklet scheduling, which touches per-cpu vars. + * NOTE: After cpu_exit_clear, per-cpu variables may no longer accessible, + * as they may be freed at any time if offline CPUs don't get parked. In + * this case, heap corruption or #PF can occur (when heap debugging is + * enabled). For example, even printk() can involve tasklet scheduling, + * which touches per-cpu vars. * * Consider very carefully when adding code to *dead_idle. Most hypervisor * subsystems are unsafe to call. diff --git a/xen/arch/x86/genapic/x2apic.c b/xen/arch/x86/genapic/x2apic.c index 4779b0d0d5..d997806272 100644 --- a/xen/arch/x86/genapic/x2apic.c +++ b/xen/arch/x86/genapic/x2apic.c @@ -201,18 +201,21 @@ static int update_clusterinfo( if ( !cluster_cpus_spare ) cluster_cpus_spare = xzalloc(cpumask_t); if ( !cluster_cpus_spare || - !alloc_cpumask_var(&per_cpu(scratch_mask, cpu)) ) + !cond_alloc_cpumask_var(&per_cpu(scratch_mask, cpu)) ) err = -ENOMEM; break; case CPU_UP_CANCELED: case CPU_DEAD: + case CPU_REMOVE: + if ( park_offline_cpus == (action != CPU_REMOVE) ) + break; if ( per_cpu(cluster_cpus, cpu) ) { cpumask_clear_cpu(cpu, per_cpu(cluster_cpus, cpu)); if ( cpumask_empty(per_cpu(cluster_cpus, cpu)) ) - xfree(per_cpu(cluster_cpus, cpu)); + XFREE(per_cpu(cluster_cpus, cpu)); } - free_cpumask_var(per_cpu(scratch_mask, cpu)); + FREE_CPUMASK_VAR(per_cpu(scratch_mask, cpu)); break; } diff --git a/xen/arch/x86/percpu.c b/xen/arch/x86/percpu.c index c9997b7937..8be4ebddf4 100644 --- a/xen/arch/x86/percpu.c +++ b/xen/arch/x86/percpu.c @@ -28,7 +28,7 @@ static int init_percpu_area(unsigned int cpu) char *p; if ( __per_cpu_offset[cpu] != INVALID_PERCPU_AREA ) - return -EBUSY; + return 0; if ( (p = alloc_xenheap_pages(PERCPU_ORDER, 0)) == NULL ) return -ENOMEM; @@ -76,9 +76,12 @@ static int cpu_percpu_callback( break; case CPU_UP_CANCELED: case CPU_DEAD: - free_percpu_area(cpu); + if ( !park_offline_cpus ) + free_percpu_area(cpu); break; - default: + case CPU_REMOVE: + if ( park_offline_cpus ) + free_percpu_area(cpu); break; } diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c index 78ba73578a..7e76cc3d68 100644 --- a/xen/arch/x86/smpboot.c +++ b/xen/arch/x86/smpboot.c @@ -63,6 +63,8 @@ static cpumask_t scratch_cpu0mask; cpumask_t cpu_online_map __read_mostly; EXPORT_SYMBOL(cpu_online_map); +bool __read_mostly park_offline_cpus; + unsigned int __read_mostly nr_sockets; cpumask_t **__read_mostly socket_cpumask; static cpumask_t *secondary_socket_cpumask; @@ -895,7 +897,14 @@ static void cleanup_cpu_root_pgt(unsigned int cpu) } } -static void cpu_smpboot_free(unsigned int cpu) +/* + * The 'remove' boolean controls whether a CPU is just getting offlined (and + * parked), or outright removed / offlined without parking. Parked CPUs need + * things like their stack, GDT, IDT, TSS, and per-CPU data still available. + * A few other items, in particular CPU masks, are also retained, as it's + * difficult to prove that they're entirely unreferenced from parked CPUs. + */ +static void cpu_smpboot_free(unsigned int cpu, bool remove) { unsigned int order, socket = cpu_to_socket(cpu); struct cpuinfo_x86 *c = cpu_data; @@ -906,15 +915,19 @@ static void cpu_smpboot_free(unsigned int cpu) socket_cpumask[socket] = NULL; } - c[cpu].phys_proc_id = XEN_INVALID_SOCKET_ID; - c[cpu].cpu_core_id = XEN_INVALID_CORE_ID; - c[cpu].compute_unit_id = INVALID_CUID; cpumask_clear_cpu(cpu, &cpu_sibling_setup_map); - free_cpumask_var(per_cpu(cpu_sibling_mask, cpu)); - free_cpumask_var(per_cpu(cpu_core_mask, cpu)); - if ( per_cpu(scratch_cpumask, cpu) != &scratch_cpu0mask ) - free_cpumask_var(per_cpu(scratch_cpumask, cpu)); + if ( remove ) + { + c[cpu].phys_proc_id = XEN_INVALID_SOCKET_ID; + c[cpu].cpu_core_id = XEN_INVALID_CORE_ID; + c[cpu].compute_unit_id = INVALID_CUID; + + FREE_CPUMASK_VAR(per_cpu(cpu_sibling_mask, cpu)); + FREE_CPUMASK_VAR(per_cpu(cpu_core_mask, cpu)); + if ( per_cpu(scratch_cpumask, cpu) != &scratch_cpu0mask ) + FREE_CPUMASK_VAR(per_cpu(scratch_cpumask, cpu)); + } cleanup_cpu_root_pgt(cpu); @@ -936,19 +949,21 @@ static void cpu_smpboot_free(unsigned int cpu) } order = get_order_from_pages(NR_RESERVED_GDT_PAGES); - free_xenheap_pages(per_cpu(gdt_table, cpu), order); + if ( remove ) + FREE_XENHEAP_PAGES(per_cpu(gdt_table, cpu), order); free_xenheap_pages(per_cpu(compat_gdt_table, cpu), order); - order = get_order_from_bytes(IDT_ENTRIES * sizeof(idt_entry_t)); - free_xenheap_pages(idt_tables[cpu], order); - idt_tables[cpu] = NULL; - - if ( stack_base[cpu] != NULL ) + if ( remove ) { - memguard_unguard_stack(stack_base[cpu]); - free_xenheap_pages(stack_base[cpu], STACK_ORDER); - stack_base[cpu] = NULL; + order = get_order_from_bytes(IDT_ENTRIES * sizeof(idt_entry_t)); + FREE_XENHEAP_PAGES(idt_tables[cpu], order); + + if ( stack_base[cpu] ) + { + memguard_unguard_stack(stack_base[cpu]); + FREE_XENHEAP_PAGES(stack_base[cpu], STACK_ORDER); + } } } @@ -963,15 +978,17 @@ static int cpu_smpboot_alloc(unsigned int cpu) if ( node != NUMA_NO_NODE ) memflags = MEMF_node(node); - stack_base[cpu] = alloc_xenheap_pages(STACK_ORDER, memflags); + if ( stack_base[cpu] == NULL ) + stack_base[cpu] = alloc_xenheap_pages(STACK_ORDER, memflags); if ( stack_base[cpu] == NULL ) goto out; memguard_guard_stack(stack_base[cpu]); order = get_order_from_pages(NR_RESERVED_GDT_PAGES); - per_cpu(gdt_table, cpu) = gdt = alloc_xenheap_pages(order, memflags); + gdt = per_cpu(gdt_table, cpu) ?: alloc_xenheap_pages(order, memflags); if ( gdt == NULL ) goto out; + per_cpu(gdt_table, cpu) = gdt; memcpy(gdt, boot_cpu_gdt_table, NR_RESERVED_GDT_PAGES * PAGE_SIZE); BUILD_BUG_ON(NR_CPUS > 0x10000); gdt[PER_CPU_GDT_ENTRY - FIRST_RESERVED_GDT_ENTRY].a = cpu; @@ -983,7 +1000,8 @@ static int cpu_smpboot_alloc(unsigned int cpu) gdt[PER_CPU_GDT_ENTRY - FIRST_RESERVED_GDT_ENTRY].a = cpu; order = get_order_from_bytes(IDT_ENTRIES * sizeof(idt_entry_t)); - idt_tables[cpu] = alloc_xenheap_pages(order, memflags); + if ( idt_tables[cpu] == NULL ) + idt_tables[cpu] = alloc_xenheap_pages(order, memflags); if ( idt_tables[cpu] == NULL ) goto out; memcpy(idt_tables[cpu], idt_table, IDT_ENTRIES * sizeof(idt_entry_t)); @@ -1011,16 +1029,16 @@ static int cpu_smpboot_alloc(unsigned int cpu) (secondary_socket_cpumask = xzalloc(cpumask_t)) == NULL ) goto out; - if ( !(zalloc_cpumask_var(&per_cpu(cpu_sibling_mask, cpu)) && - zalloc_cpumask_var(&per_cpu(cpu_core_mask, cpu)) && - alloc_cpumask_var(&per_cpu(scratch_cpumask, cpu))) ) + if ( !(cond_zalloc_cpumask_var(&per_cpu(cpu_sibling_mask, cpu)) && + cond_zalloc_cpumask_var(&per_cpu(cpu_core_mask, cpu)) && + cond_alloc_cpumask_var(&per_cpu(scratch_cpumask, cpu))) ) goto out; rc = 0; out: if ( rc ) - cpu_smpboot_free(cpu); + cpu_smpboot_free(cpu, true); return rc; } @@ -1038,9 +1056,10 @@ static int cpu_smpboot_callback( break; case CPU_UP_CANCELED: case CPU_DEAD: - cpu_smpboot_free(cpu); + cpu_smpboot_free(cpu, !park_offline_cpus); break; - default: + case CPU_REMOVE: + cpu_smpboot_free(cpu, true); break; } diff --git a/xen/include/asm-x86/smp.h b/xen/include/asm-x86/smp.h index 4e5f673fec..09c55458df 100644 --- a/xen/include/asm-x86/smp.h +++ b/xen/include/asm-x86/smp.h @@ -26,6 +26,8 @@ DECLARE_PER_CPU(cpumask_var_t, cpu_sibling_mask); DECLARE_PER_CPU(cpumask_var_t, cpu_core_mask); DECLARE_PER_CPU(cpumask_var_t, scratch_cpumask); +extern bool park_offline_cpus; + void smp_send_nmi_allbutself(void); void send_IPI_mask(const cpumask_t *, int vector); diff --git a/xen/include/xen/cpu.h b/xen/include/xen/cpu.h index ffefc09f8e..2fe3ec05d8 100644 --- a/xen/include/xen/cpu.h +++ b/xen/include/xen/cpu.h @@ -47,6 +47,8 @@ void register_cpu_notifier(struct notifier_block *nb); #define CPU_DYING (0x0007 | NOTIFY_REVERSE) /* CPU_DEAD: CPU is dead. */ #define CPU_DEAD (0x0008 | NOTIFY_REVERSE) +/* CPU_REMOVE: CPU was removed. */ +#define CPU_REMOVE (0x0009 | NOTIFY_REVERSE) /* Perform CPU hotplug. May return -EAGAIN. */ int cpu_down(unsigned int cpu); diff --git a/xen/include/xen/cpumask.h b/xen/include/xen/cpumask.h index 42340a098e..4a11bcc3f3 100644 --- a/xen/include/xen/cpumask.h +++ b/xen/include/xen/cpumask.h @@ -351,16 +351,35 @@ static inline bool_t alloc_cpumask_var(cpumask_var_t *mask) return *mask != NULL; } +static inline bool cond_alloc_cpumask_var(cpumask_var_t *mask) +{ + if (*mask == NULL) + *mask = _xmalloc(nr_cpumask_bits / 8, sizeof(long)); + return *mask != NULL; +} + static inline bool_t zalloc_cpumask_var(cpumask_var_t *mask) { *(void **)mask = _xzalloc(nr_cpumask_bits / 8, sizeof(long)); return *mask != NULL; } +static inline bool cond_zalloc_cpumask_var(cpumask_var_t *mask) +{ + if (*mask == NULL) + *mask = _xzalloc(nr_cpumask_bits / 8, sizeof(long)); + else + cpumask_clear(*mask); + return *mask != NULL; +} + static inline void free_cpumask_var(cpumask_var_t mask) { xfree(mask); } + +/* Free an allocated mask, and zero the pointer to it. */ +#define FREE_CPUMASK_VAR(m) XFREE(m) #else typedef cpumask_t cpumask_var_t[1]; @@ -368,16 +387,20 @@ static inline bool_t alloc_cpumask_var(cpumask_var_t *mask) { return 1; } +#define cond_alloc_cpumask_var alloc_cpumask_var static inline bool_t zalloc_cpumask_var(cpumask_var_t *mask) { cpumask_clear(*mask); return 1; } +#define cond_zalloc_cpumask_var zalloc_cpumask_var static inline void free_cpumask_var(cpumask_var_t mask) { } + +#define FREE_CPUMASK_VAR(m) free_cpumask_var(m) #endif #if NR_CPUS > 1 diff --git a/xen/include/xen/mm.h b/xen/include/xen/mm.h index e928551c91..24654e8e22 100644 --- a/xen/include/xen/mm.h +++ b/xen/include/xen/mm.h @@ -162,6 +162,14 @@ void free_xenheap_pages(void *v, unsigned int order); bool scrub_free_pages(void); #define alloc_xenheap_page() (alloc_xenheap_pages(0,0)) #define free_xenheap_page(v) (free_xenheap_pages(v,0)) + +/* Free an allocation, and zero the pointer to it. */ +#define FREE_XENHEAP_PAGES(p, o) do { \ + free_xenheap_pages(p, o); \ + (p) = NULL; \ +} while ( false ) +#define FREE_XENHEAP_PAGE(p) FREE_XENHEAP_PAGES(p, 0) + /* Map machine page range in Xen virtual address space. */ int map_pages_to_xen( unsigned long virt, diff --git a/xen/include/xen/xmalloc.h b/xen/include/xen/xmalloc.h index 6e75ad6986..b486fe4b06 100644 --- a/xen/include/xen/xmalloc.h +++ b/xen/include/xen/xmalloc.h @@ -42,6 +42,12 @@ /* Free any of the above. */ extern void xfree(void *); +/* Free an allocation, and zero the pointer to it. */ +#define XFREE(p) do { \ + xfree(p); \ + (p) = NULL; \ +} while ( false ) + /* Underlying functions */ extern void *_xmalloc(unsigned long size, unsigned long align); extern void *_xzalloc(unsigned long size, unsigned long align);