Merge branch 'tip/perf/urgent'
[linux-2.6-microblaze.git] / kernel / events / core.c
index d27ffd8..0825098 100644 (file)
@@ -1965,6 +1965,7 @@ static void perf_group_attach(struct perf_event *event)
 
        list_add_tail(&event->sibling_list, &group_leader->sibling_list);
        group_leader->nr_siblings++;
+       group_leader->group_generation++;
 
        perf_event__header_size(group_leader);
 
@@ -2155,6 +2156,7 @@ 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++;
                goto out;
        }
 
@@ -4826,6 +4828,11 @@ find_get_pmu_context(struct pmu *pmu, struct perf_event_context *ctx,
        void *task_ctx_data = NULL;
 
        if (!ctx->task) {
+               /*
+                * perf_pmu_migrate_context() / __perf_pmu_install_event()
+                * relies on the fact that find_get_pmu_context() cannot fail
+                * for CPU contexts.
+                */
                struct perf_cpu_pmu_context *cpc;
 
                cpc = per_cpu_ptr(pmu->cpu_pmu_context, event->cpu);
@@ -5457,7 +5464,7 @@ static int __perf_read_group_add(struct perf_event *leader,
                                        u64 read_format, u64 *values)
 {
        struct perf_event_context *ctx = leader->ctx;
-       struct perf_event *sub;
+       struct perf_event *sub, *parent;
        unsigned long flags;
        int n = 1; /* skip @nr */
        int ret;
@@ -5467,6 +5474,33 @@ static int __perf_read_group_add(struct perf_event *leader,
                return ret;
 
        raw_spin_lock_irqsave(&ctx->lock, flags);
+       /*
+        * Verify the grouping between the parent and child (inherited)
+        * events is still in tact.
+        *
+        * Specifically:
+        *  - leader->ctx->lock pins leader->sibling_list
+        *  - parent->child_mutex pins parent->child_list
+        *  - parent->ctx->mutex pins parent->sibling_list
+        *
+        * Because parent->ctx != leader->ctx (and child_list nests inside
+        * ctx->mutex), group destruction is not atomic between children, also
+        * see perf_event_release_kernel(). Additionally, parent can grow the
+        * group.
+        *
+        * Therefore it is possible to have parent and child groups in a
+        * different configuration and summing over such a beast makes no sense
+        * what so ever.
+        *
+        * Reject this.
+        */
+       parent = leader->parent;
+       if (parent &&
+           (parent->group_generation != leader->group_generation ||
+            parent->nr_siblings != leader->nr_siblings)) {
+               ret = -ECHILD;
+               goto unlock;
+       }
 
        /*
         * Since we co-schedule groups, {enabled,running} times of siblings
@@ -5500,8 +5534,9 @@ static int __perf_read_group_add(struct perf_event *leader,
                        values[n++] = atomic64_read(&sub->lost_samples);
        }
 
+unlock:
        raw_spin_unlock_irqrestore(&ctx->lock, flags);
-       return 0;
+       return ret;
 }
 
 static int perf_read_group(struct perf_event *event,
@@ -5520,10 +5555,6 @@ static int perf_read_group(struct perf_event *event,
 
        values[0] = 1 + leader->nr_siblings;
 
-       /*
-        * By locking the child_mutex of the leader we effectively
-        * lock the child list of all siblings.. XXX explain how.
-        */
        mutex_lock(&leader->child_mutex);
 
        ret = __perf_read_group_add(leader, read_format, values);
@@ -12871,6 +12902,9 @@ static void __perf_pmu_install_event(struct pmu *pmu,
                                     int cpu, struct perf_event *event)
 {
        struct perf_event_pmu_context *epc;
+       struct perf_event_context *old_ctx = event->ctx;
+
+       get_ctx(ctx); /* normally find_get_context() */
 
        event->cpu = cpu;
        epc = find_get_pmu_context(pmu, ctx, event);
@@ -12879,6 +12913,11 @@ static void __perf_pmu_install_event(struct pmu *pmu,
        if (event->state >= PERF_EVENT_STATE_OFF)
                event->state = PERF_EVENT_STATE_INACTIVE;
        perf_install_in_context(ctx, event, cpu);
+
+       /*
+        * Now that event->ctx is updated and visible, put the old ctx.
+        */
+       put_ctx(old_ctx);
 }
 
 static void __perf_pmu_install(struct perf_event_context *ctx,
@@ -12917,6 +12956,10 @@ void perf_pmu_migrate_context(struct pmu *pmu, int src_cpu, int dst_cpu)
        struct perf_event_context *src_ctx, *dst_ctx;
        LIST_HEAD(events);
 
+       /*
+        * Since per-cpu context is persistent, no need to grab an extra
+        * reference.
+        */
        src_ctx = &per_cpu_ptr(&perf_cpu_context, src_cpu)->ctx;
        dst_ctx = &per_cpu_ptr(&perf_cpu_context, dst_cpu)->ctx;
 
@@ -13371,6 +13414,8 @@ static int inherit_group(struct perf_event *parent_event,
                    !perf_get_aux_event(child_ctr, leader))
                        return -EINVAL;
        }
+       if (leader)
+               leader->group_generation = parent_event->group_generation;
        return 0;
 }