perf: Fix cgroup state vs ERROR

While chasing down a missing perf_cgroup_event_disable() elsewhere,
Leo Yan found that both perf_put_aux_event() and
perf_remove_sibling_event() were also missing one.

Specifically, the rule is that events that switch to OFF,ERROR need to
call perf_cgroup_event_disable().

Unify the disable paths to ensure this.

Fixes: ab43762ef010 ("perf: Allow normal events to output AUX data")
Fixes: 9f0c4fa111dc ("perf/core: Add a new PERF_EV_CAP_SIBLING event capability")
Reported-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/20250605123343.GD35970@noisy.programming.kicks-ass.net

+30 -21
+30 -21
kernel/events/core.c
··· 2149 2149 } 2150 2150 2151 2151 static void put_event(struct perf_event *event); 2152 - static void event_sched_out(struct perf_event *event, 2153 - struct perf_event_context *ctx); 2152 + static void __event_disable(struct perf_event *event, 2153 + struct perf_event_context *ctx, 2154 + enum perf_event_state state); 2154 2155 2155 2156 static void perf_put_aux_event(struct perf_event *event) 2156 2157 { ··· 2184 2183 * state so that we don't try to schedule it again. Note 2185 2184 * that perf_event_enable() will clear the ERROR status. 2186 2185 */ 2187 - event_sched_out(iter, ctx); 2188 - perf_event_set_state(event, PERF_EVENT_STATE_ERROR); 2186 + __event_disable(iter, ctx, PERF_EVENT_STATE_ERROR); 2189 2187 } 2190 2188 } 2191 2189 ··· 2242 2242 &event->pmu_ctx->flexible_active; 2243 2243 } 2244 2244 2245 - /* 2246 - * Events that have PERF_EV_CAP_SIBLING require being part of a group and 2247 - * cannot exist on their own, schedule them out and move them into the ERROR 2248 - * state. Also see _perf_event_enable(), it will not be able to recover 2249 - * this ERROR state. 2250 - */ 2251 - static inline void perf_remove_sibling_event(struct perf_event *event) 2252 - { 2253 - event_sched_out(event, event->ctx); 2254 - perf_event_set_state(event, PERF_EVENT_STATE_ERROR); 2255 - } 2256 - 2257 2245 static void perf_group_detach(struct perf_event *event) 2258 2246 { 2259 2247 struct perf_event *leader = event->group_leader; ··· 2277 2289 */ 2278 2290 list_for_each_entry_safe(sibling, tmp, &event->sibling_list, sibling_list) { 2279 2291 2292 + /* 2293 + * Events that have PERF_EV_CAP_SIBLING require being part of 2294 + * a group and cannot exist on their own, schedule them out 2295 + * and move them into the ERROR state. Also see 2296 + * _perf_event_enable(), it will not be able to recover this 2297 + * ERROR state. 2298 + */ 2280 2299 if (sibling->event_caps & PERF_EV_CAP_SIBLING) 2281 - perf_remove_sibling_event(sibling); 2300 + __event_disable(sibling, ctx, PERF_EVENT_STATE_ERROR); 2282 2301 2283 2302 sibling->group_leader = sibling; 2284 2303 list_del_init(&sibling->sibling_list); ··· 2557 2562 event_function_call(event, __perf_remove_from_context, (void *)flags); 2558 2563 } 2559 2564 2565 + static void __event_disable(struct perf_event *event, 2566 + struct perf_event_context *ctx, 2567 + enum perf_event_state state) 2568 + { 2569 + event_sched_out(event, ctx); 2570 + perf_cgroup_event_disable(event, ctx); 2571 + perf_event_set_state(event, state); 2572 + } 2573 + 2560 2574 /* 2561 2575 * Cross CPU call to disable a performance event 2562 2576 */ ··· 2580 2576 perf_pmu_disable(event->pmu_ctx->pmu); 2581 2577 ctx_time_update_event(ctx, event); 2582 2578 2579 + /* 2580 + * When disabling a group leader, the whole group becomes ineligible 2581 + * to run, so schedule out the full group. 2582 + */ 2583 2583 if (event == event->group_leader) 2584 2584 group_sched_out(event, ctx); 2585 - else 2586 - event_sched_out(event, ctx); 2587 2585 2588 - perf_event_set_state(event, PERF_EVENT_STATE_OFF); 2589 - perf_cgroup_event_disable(event, ctx); 2586 + /* 2587 + * But only mark the leader OFF; the siblings will remain 2588 + * INACTIVE. 2589 + */ 2590 + __event_disable(event, ctx, PERF_EVENT_STATE_OFF); 2590 2591 2591 2592 perf_pmu_enable(event->pmu_ctx->pmu); 2592 2593 }