diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c index c1e932d98f75..52710f11d473 100644 --- a/arch/arm64/kernel/perf_event.c +++ b/arch/arm64/kernel/perf_event.c @@ -779,8 +779,8 @@ static irqreturn_t armv8pmu_handle_irq(int irq_num, void *dev) struct perf_event *event = cpuc->events[idx]; struct hw_perf_event *hwc; - /* Ignore if we don't have an event. */ - if (!event) + /* Ignore if we don't have an event or if it's a zombie event */ + if (!event || event->state == PERF_EVENT_STATE_ZOMBIE) continue; /* diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c index ad3e1e755136..ffcd00153ee2 100644 --- a/drivers/perf/arm_pmu.c +++ b/drivers/perf/arm_pmu.c @@ -743,6 +743,15 @@ static void cpu_pm_pmu_setup(struct arm_pmu *armpmu, unsigned long cmd) continue; event = hw_events->events[idx]; + if (!event) + continue; + + /* + * Check if an attempt was made to free this event during + * the CPU went offline. + */ + if (event->state == PERF_EVENT_STATE_ZOMBIE) + continue; switch (cmd) { case CPU_PM_ENTER: diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 33440fa5692d..70936bff449b 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -497,7 +497,8 @@ struct perf_addr_filters_head { * enum perf_event_active_state - the states of a event */ enum perf_event_active_state { - PERF_EVENT_STATE_DEAD = -4, + PERF_EVENT_STATE_DEAD = -5, + PERF_EVENT_STATE_ZOMBIE = -4, PERF_EVENT_STATE_EXIT = -3, PERF_EVENT_STATE_ERROR = -2, PERF_EVENT_STATE_OFF = -1, @@ -720,6 +721,7 @@ struct perf_event { /* Is this event shared with other events */ bool shared; + struct list_head zombie_entry; #endif /* CONFIG_PERF_EVENTS */ }; diff --git a/kernel/events/core.c b/kernel/events/core.c index ed27a8c1bd1a..712ba4ea4d00 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -4262,6 +4262,14 @@ static void put_event(struct perf_event *event) _free_event(event); } +/* + * Maintain a zombie list to collect all the zombie events + */ +#if defined CONFIG_HOTPLUG_CPU || defined CONFIG_KEXEC_CORE +static LIST_HEAD(zombie_list); +static DEFINE_SPINLOCK(zombie_list_lock); +#endif + /* * Kill an event dead; while event:refcount will preserve the event * object, it will not preserve its functionality. Once the last 'user' @@ -4272,6 +4280,26 @@ int perf_event_release_kernel(struct perf_event *event) struct perf_event_context *ctx = event->ctx; struct perf_event *child, *tmp; + /* + * If the cpu associated to this event is offline, set the event as a + * zombie event. The cleanup of the cpu would be done if the CPU is + * back online. + */ +#if defined CONFIG_HOTPLUG_CPU || defined CONFIG_KEXEC_CORE + if (!cpu_online(event->cpu)) { + if (event->state == PERF_EVENT_STATE_ZOMBIE) + return 0; + + event->state = PERF_EVENT_STATE_ZOMBIE; + + spin_lock(&zombie_list_lock); + list_add_tail(&event->zombie_entry, &zombie_list); + spin_unlock(&zombie_list_lock); + + return 0; + } +#endif + /* * If we got here through err_file: fput(event_file); we will not have * attached to a context yet. @@ -9388,6 +9416,7 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu, INIT_LIST_HEAD(&event->rb_entry); INIT_LIST_HEAD(&event->active_entry); INIT_LIST_HEAD(&event->addr_filters.list); + INIT_LIST_HEAD(&event->zombie_entry); INIT_HLIST_NODE(&event->hlist_entry); @@ -11043,6 +11072,32 @@ check_hotplug_start_event(struct perf_event *event) event->pmu->start(event, 0); } +static void perf_event_zombie_cleanup(unsigned int cpu) +{ + struct perf_event *event, *tmp; + + spin_lock(&zombie_list_lock); + + list_for_each_entry_safe(event, tmp, &zombie_list, zombie_entry) { + if (event->cpu != cpu) + continue; + + list_del(&event->zombie_entry); + spin_unlock(&zombie_list_lock); + + /* + * The detachment of the event with the + * PMU expects it to be in an active state + */ + event->state = PERF_EVENT_STATE_ACTIVE; + perf_event_release_kernel(event); + + spin_lock(&zombie_list_lock); + } + + spin_unlock(&zombie_list_lock); +} + static int perf_event_start_swevents(unsigned int cpu) { struct perf_event_context *ctx; @@ -11050,6 +11105,8 @@ static int perf_event_start_swevents(unsigned int cpu) struct perf_event *event; int idx; + perf_event_zombie_cleanup(cpu); + idx = srcu_read_lock(&pmus_srcu); list_for_each_entry_rcu(pmu, &pmus, entry) { ctx = &per_cpu_ptr(pmu->pmu_cpu_context, cpu)->ctx;