diff --git a/kernel/events/core.c b/kernel/events/core.c index 9a43f9f63898bbb4c9bc8d8ad2a2ca6322af1e44..410f096131a5ef0b4cb2ce4cfda7b752c6e87a8f 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2264,6 +2264,34 @@ static inline struct list_head *get_event_list(struct perf_event *event) &event->pmu_ctx->flexible_active; } +/* @sibling must already be unlinked from its old leader's sibling_list. */ +static void perf_promote_sibling_to_leader(struct perf_event *sibling, + struct perf_event_context *ctx, + int group_caps) +{ + /* + * Events that have PERF_EV_CAP_SIBLING require being part of + * a group and cannot exist on their own, schedule them out + * and move them into the ERROR state. Also see + * _perf_event_enable(), it will not be able to recover this + * ERROR state. + */ + if (sibling->event_caps & PERF_EV_CAP_SIBLING) + __event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR); + + sibling->group_leader = sibling; + sibling->group_caps = group_caps; + + if (sibling->attach_state & PERF_ATTACH_CONTEXT) { + add_event_to_groups(sibling, ctx); + + if (sibling->state == PERF_EVENT_STATE_ACTIVE) + list_add_tail(&sibling->active_list, get_event_list(sibling)); + } + + perf_event__header_size(sibling); +} + static void perf_group_detach(struct perf_event *event) { struct perf_event *leader = event->group_leader; @@ -2287,8 +2315,9 @@ static void perf_group_detach(struct perf_event *event) */ if (leader != event) { list_del_init(&event->sibling_list); - event->group_leader->nr_siblings--; - event->group_leader->group_generation++; + leader->nr_siblings--; + leader->group_generation++; + perf_promote_sibling_to_leader(event, ctx, event->event_caps); goto out; } @@ -2298,32 +2327,14 @@ static void perf_group_detach(struct perf_event *event) * to whatever list we are on. */ list_for_each_entry_safe(sibling, tmp, &event->sibling_list, sibling_list) { - - /* - * Events that have PERF_EV_CAP_SIBLING require being part of - * a group and cannot exist on their own, schedule them out - * and move them into the ERROR state. Also see - * _perf_event_enable(), it will not be able to recover this - * ERROR state. - */ - if (sibling->event_caps & PERF_EV_CAP_SIBLING) - __event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR); - - sibling->group_leader = sibling; list_del_init(&sibling->sibling_list); /* Inherit group flags from the previous leader */ - sibling->group_caps = event->group_caps; - - if (sibling->attach_state & PERF_ATTACH_CONTEXT) { - add_event_to_groups(sibling, event->ctx); - - if (sibling->state == PERF_EVENT_STATE_ACTIVE) - list_add_tail(&sibling->active_list, get_event_list(sibling)); - } + perf_promote_sibling_to_leader(sibling, ctx, event->group_caps); WARN_ON_ONCE(sibling->ctx != event->ctx); } + event->nr_siblings = 0; out: for_each_sibling_event(tmp, leader) @@ -2490,12 +2501,9 @@ __perf_remove_from_context(struct perf_event *event, event->pending_disable = 1; state = PERF_EVENT_STATE_DEAD; } - event_sched_out(event, ctx); - if (event->state > PERF_EVENT_STATE_OFF) - perf_cgroup_event_disable(event, ctx); + __event_disable(event, ctx, state); - perf_event_set_state(event, min(event->state, state)); if (flags & DETACH_GROUP) perf_group_detach(event); if (flags & DETACH_CHILD) @@ -2564,8 +2572,9 @@ static void __event_disable(struct perf_event *event, enum perf_event_state state) { event_sched_out(event, ctx); - perf_cgroup_event_disable(event, ctx); - perf_event_set_state(event, state); + if (event->state > PERF_EVENT_STATE_OFF) + perf_cgroup_event_disable(event, ctx); + perf_event_set_state(event, min(event->state, state)); } /*