diff options
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/perf_event.c | 58 |
1 files changed, 46 insertions, 12 deletions
diff --git a/kernel/perf_event.c b/kernel/perf_event.c index a4fa381db3c2..511677bc1c6a 100644 --- a/kernel/perf_event.c +++ b/kernel/perf_event.c | |||
@@ -4066,19 +4066,46 @@ static inline u64 swevent_hash(u64 type, u32 event_id) | |||
4066 | return hash_64(val, SWEVENT_HLIST_BITS); | 4066 | return hash_64(val, SWEVENT_HLIST_BITS); |
4067 | } | 4067 | } |
4068 | 4068 | ||
4069 | static struct hlist_head * | 4069 | static inline struct hlist_head * |
4070 | find_swevent_head(struct perf_cpu_context *ctx, u64 type, u32 event_id) | 4070 | __find_swevent_head(struct swevent_hlist *hlist, u64 type, u32 event_id) |
4071 | { | 4071 | { |
4072 | u64 hash; | 4072 | u64 hash = swevent_hash(type, event_id); |
4073 | struct swevent_hlist *hlist; | 4073 | |
4074 | return &hlist->heads[hash]; | ||
4075 | } | ||
4074 | 4076 | ||
4075 | hash = swevent_hash(type, event_id); | 4077 | /* For the read side: events when they trigger */ |
4078 | static inline struct hlist_head * | ||
4079 | find_swevent_head_rcu(struct perf_cpu_context *ctx, u64 type, u32 event_id) | ||
4080 | { | ||
4081 | struct swevent_hlist *hlist; | ||
4076 | 4082 | ||
4077 | hlist = rcu_dereference(ctx->swevent_hlist); | 4083 | hlist = rcu_dereference(ctx->swevent_hlist); |
4078 | if (!hlist) | 4084 | if (!hlist) |
4079 | return NULL; | 4085 | return NULL; |
4080 | 4086 | ||
4081 | return &hlist->heads[hash]; | 4087 | return __find_swevent_head(hlist, type, event_id); |
4088 | } | ||
4089 | |||
4090 | /* For the event head insertion and removal in the hlist */ | ||
4091 | static inline struct hlist_head * | ||
4092 | find_swevent_head(struct perf_cpu_context *ctx, struct perf_event *event) | ||
4093 | { | ||
4094 | struct swevent_hlist *hlist; | ||
4095 | u32 event_id = event->attr.config; | ||
4096 | u64 type = event->attr.type; | ||
4097 | |||
4098 | /* | ||
4099 | * Event scheduling is always serialized against hlist allocation | ||
4100 | * and release. Which makes the protected version suitable here. | ||
4101 | * The context lock guarantees that. | ||
4102 | */ | ||
4103 | hlist = rcu_dereference_protected(ctx->swevent_hlist, | ||
4104 | lockdep_is_held(&event->ctx->lock)); | ||
4105 | if (!hlist) | ||
4106 | return NULL; | ||
4107 | |||
4108 | return __find_swevent_head(hlist, type, event_id); | ||
4082 | } | 4109 | } |
4083 | 4110 | ||
4084 | static void do_perf_sw_event(enum perf_type_id type, u32 event_id, | 4111 | static void do_perf_sw_event(enum perf_type_id type, u32 event_id, |
@@ -4095,7 +4122,7 @@ static void do_perf_sw_event(enum perf_type_id type, u32 event_id, | |||
4095 | 4122 | ||
4096 | rcu_read_lock(); | 4123 | rcu_read_lock(); |
4097 | 4124 | ||
4098 | head = find_swevent_head(cpuctx, type, event_id); | 4125 | head = find_swevent_head_rcu(cpuctx, type, event_id); |
4099 | 4126 | ||
4100 | if (!head) | 4127 | if (!head) |
4101 | goto end; | 4128 | goto end; |
@@ -4178,7 +4205,7 @@ static int perf_swevent_enable(struct perf_event *event) | |||
4178 | perf_swevent_set_period(event); | 4205 | perf_swevent_set_period(event); |
4179 | } | 4206 | } |
4180 | 4207 | ||
4181 | head = find_swevent_head(cpuctx, event->attr.type, event->attr.config); | 4208 | head = find_swevent_head(cpuctx, event); |
4182 | if (WARN_ON_ONCE(!head)) | 4209 | if (WARN_ON_ONCE(!head)) |
4183 | return -EINVAL; | 4210 | return -EINVAL; |
4184 | 4211 | ||
@@ -4366,6 +4393,14 @@ static const struct pmu perf_ops_task_clock = { | |||
4366 | .read = task_clock_perf_event_read, | 4393 | .read = task_clock_perf_event_read, |
4367 | }; | 4394 | }; |
4368 | 4395 | ||
4396 | /* Deref the hlist from the update side */ | ||
4397 | static inline struct swevent_hlist * | ||
4398 | swevent_hlist_deref(struct perf_cpu_context *cpuctx) | ||
4399 | { | ||
4400 | return rcu_dereference_protected(cpuctx->swevent_hlist, | ||
4401 | lockdep_is_held(&cpuctx->hlist_mutex)); | ||
4402 | } | ||
4403 | |||
4369 | static void swevent_hlist_release_rcu(struct rcu_head *rcu_head) | 4404 | static void swevent_hlist_release_rcu(struct rcu_head *rcu_head) |
4370 | { | 4405 | { |
4371 | struct swevent_hlist *hlist; | 4406 | struct swevent_hlist *hlist; |
@@ -4376,12 +4411,11 @@ static void swevent_hlist_release_rcu(struct rcu_head *rcu_head) | |||
4376 | 4411 | ||
4377 | static void swevent_hlist_release(struct perf_cpu_context *cpuctx) | 4412 | static void swevent_hlist_release(struct perf_cpu_context *cpuctx) |
4378 | { | 4413 | { |
4379 | struct swevent_hlist *hlist; | 4414 | struct swevent_hlist *hlist = swevent_hlist_deref(cpuctx); |
4380 | 4415 | ||
4381 | if (!cpuctx->swevent_hlist) | 4416 | if (!hlist) |
4382 | return; | 4417 | return; |
4383 | 4418 | ||
4384 | hlist = cpuctx->swevent_hlist; | ||
4385 | rcu_assign_pointer(cpuctx->swevent_hlist, NULL); | 4419 | rcu_assign_pointer(cpuctx->swevent_hlist, NULL); |
4386 | call_rcu(&hlist->rcu_head, swevent_hlist_release_rcu); | 4420 | call_rcu(&hlist->rcu_head, swevent_hlist_release_rcu); |
4387 | } | 4421 | } |
@@ -4418,7 +4452,7 @@ static int swevent_hlist_get_cpu(struct perf_event *event, int cpu) | |||
4418 | 4452 | ||
4419 | mutex_lock(&cpuctx->hlist_mutex); | 4453 | mutex_lock(&cpuctx->hlist_mutex); |
4420 | 4454 | ||
4421 | if (!cpuctx->swevent_hlist && cpu_online(cpu)) { | 4455 | if (!swevent_hlist_deref(cpuctx) && cpu_online(cpu)) { |
4422 | struct swevent_hlist *hlist; | 4456 | struct swevent_hlist *hlist; |
4423 | 4457 | ||
4424 | hlist = kzalloc(sizeof(*hlist), GFP_KERNEL); | 4458 | hlist = kzalloc(sizeof(*hlist), GFP_KERNEL); |