Merge branch 'perf-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[sfrench/cifs-2.6.git] / kernel / events / core.c
index 0463c1151baebb612b617cbf3b189fdde1990d28..2aad959e6def727accc954163a3d8f532e3fd83b 100644 (file)
@@ -1887,6 +1887,89 @@ list_del_event(struct perf_event *event, struct perf_event_context *ctx)
        ctx->generation++;
 }
 
+static int
+perf_aux_output_match(struct perf_event *event, struct perf_event *aux_event)
+{
+       if (!has_aux(aux_event))
+               return 0;
+
+       if (!event->pmu->aux_output_match)
+               return 0;
+
+       return event->pmu->aux_output_match(aux_event);
+}
+
+static void put_event(struct perf_event *event);
+static void event_sched_out(struct perf_event *event,
+                           struct perf_cpu_context *cpuctx,
+                           struct perf_event_context *ctx);
+
+static void perf_put_aux_event(struct perf_event *event)
+{
+       struct perf_event_context *ctx = event->ctx;
+       struct perf_cpu_context *cpuctx = __get_cpu_context(ctx);
+       struct perf_event *iter;
+
+       /*
+        * If event uses aux_event tear down the link
+        */
+       if (event->aux_event) {
+               iter = event->aux_event;
+               event->aux_event = NULL;
+               put_event(iter);
+               return;
+       }
+
+       /*
+        * If the event is an aux_event, tear down all links to
+        * it from other events.
+        */
+       for_each_sibling_event(iter, event->group_leader) {
+               if (iter->aux_event != event)
+                       continue;
+
+               iter->aux_event = NULL;
+               put_event(event);
+
+               /*
+                * If it's ACTIVE, schedule it out and put it into ERROR
+                * state so that we don't try to schedule it again. Note
+                * that perf_event_enable() will clear the ERROR status.
+                */
+               event_sched_out(iter, cpuctx, ctx);
+               perf_event_set_state(event, PERF_EVENT_STATE_ERROR);
+       }
+}
+
+static int perf_get_aux_event(struct perf_event *event,
+                             struct perf_event *group_leader)
+{
+       /*
+        * Our group leader must be an aux event if we want to be
+        * an aux_output. This way, the aux event will precede its
+        * aux_output events in the group, and therefore will always
+        * schedule first.
+        */
+       if (!group_leader)
+               return 0;
+
+       if (!perf_aux_output_match(event, group_leader))
+               return 0;
+
+       if (!atomic_long_inc_not_zero(&group_leader->refcount))
+               return 0;
+
+       /*
+        * Link aux_outputs to their aux event; this is undone in
+        * perf_group_detach() by perf_put_aux_event(). When the
+        * group in torn down, the aux_output events loose their
+        * link to the aux_event and can't schedule any more.
+        */
+       event->aux_event = group_leader;
+
+       return 1;
+}
+
 static void perf_group_detach(struct perf_event *event)
 {
        struct perf_event *sibling, *tmp;
@@ -1902,6 +1985,8 @@ static void perf_group_detach(struct perf_event *event)
 
        event->attach_state &= ~PERF_ATTACH_GROUP;
 
+       perf_put_aux_event(event);
+
        /*
         * If this is a sibling, remove it from its group.
         */
@@ -10426,6 +10511,12 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
                goto err_ns;
        }
 
+       if (event->attr.aux_output &&
+           !(pmu->capabilities & PERF_PMU_CAP_AUX_OUTPUT)) {
+               err = -EOPNOTSUPP;
+               goto err_pmu;
+       }
+
        err = exclusive_event_init(event);
        if (err)
                goto err_pmu;
@@ -11082,6 +11173,8 @@ SYSCALL_DEFINE5(perf_event_open,
                }
        }
 
+       if (event->attr.aux_output && !perf_get_aux_event(event, group_leader))
+               goto err_locked;
 
        /*
         * Must be under the same ctx::mutex as perf_install_in_context(),