Fixes a kernel oops when using perf counter with multiple contexts. The
cpu hotplug notifier callback needs to have the callback notifier block
at a fixed address, but the context array may move as it is expanded.
The reason why we use a context array instead of an array of pointers is
to minimize the amount of memory accesses in the tracing hot path.
Let's special-case perf counters and put them in their own memory region
to account for cpu hotplug requirements.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
}
EXPORT_SYMBOL_GPL(lttng_find_context);
}
EXPORT_SYMBOL_GPL(lttng_find_context);
+/*
+ * Note: as we append context information, the pointer location may change.
+ */
struct lttng_ctx_field *lttng_append_context(struct lttng_ctx **ctx_p)
{
struct lttng_ctx_field *field;
struct lttng_ctx_field *lttng_append_context(struct lttng_ctx **ctx_p)
{
struct lttng_ctx_field *field;
struct lttng_type type;
};
struct lttng_type type;
};
+/*
+ * We need to keep this perf counter field separately from struct
+ * lttng_ctx_field because cpu hotplug needs fixed-location addresses.
+ */
+struct lttng_perf_counter_field {
+ struct notifier_block nb;
+ int hp_enable;
+ struct perf_event_attr *attr;
+ struct perf_event **e; /* per-cpu array */
+};
+
struct lttng_ctx_field {
struct lttng_event_field event_field;
size_t (*get_size)(size_t offset);
struct lttng_ctx_field {
struct lttng_event_field event_field;
size_t (*get_size)(size_t offset);
struct lib_ring_buffer_ctx *ctx,
struct ltt_channel *chan);
union {
struct lib_ring_buffer_ctx *ctx,
struct ltt_channel *chan);
union {
- struct {
- struct perf_event **e; /* per-cpu array */
- struct notifier_block nb;
- int hp_enable;
- struct perf_event_attr *attr;
- } perf_counter;
+ struct lttng_perf_counter_field *perf_counter;
} u;
void (*destroy)(struct lttng_ctx_field *field);
};
} u;
void (*destroy)(struct lttng_ctx_field *field);
};
struct perf_event *event;
uint64_t value;
struct perf_event *event;
uint64_t value;
- event = field->u.perf_counter.e[ctx->cpu];
+ event = field->u.perf_counter->e[ctx->cpu];
if (likely(event)) {
event->pmu->read(event);
value = local64_read(&event->count);
if (likely(event)) {
event->pmu->read(event);
value = local64_read(&event->count);
static
void lttng_destroy_perf_counter_field(struct lttng_ctx_field *field)
{
static
void lttng_destroy_perf_counter_field(struct lttng_ctx_field *field)
{
- struct perf_event **events = field->u.perf_counter.e;
+ struct perf_event **events = field->u.perf_counter->e;
int cpu;
get_online_cpus();
int cpu;
get_online_cpus();
perf_event_release_kernel(events[cpu]);
put_online_cpus();
#ifdef CONFIG_HOTPLUG_CPU
perf_event_release_kernel(events[cpu]);
put_online_cpus();
#ifdef CONFIG_HOTPLUG_CPU
- unregister_cpu_notifier(&field->u.perf_counter.nb);
+ unregister_cpu_notifier(&field->u.perf_counter->nb);
#endif
kfree(field->event_field.name);
#endif
kfree(field->event_field.name);
- kfree(field->u.perf_counter.attr);
+ kfree(field->u.perf_counter->attr);
+ kfree(field->u.perf_counter);
}
#ifdef CONFIG_HOTPLUG_CPU
}
#ifdef CONFIG_HOTPLUG_CPU
void *hcpu)
{
unsigned int cpu = (unsigned long) hcpu;
void *hcpu)
{
unsigned int cpu = (unsigned long) hcpu;
- struct lttng_ctx_field *field =
- container_of(nb, struct lttng_ctx_field, u.perf_counter.nb);
- struct perf_event **events = field->u.perf_counter.e;
- struct perf_event_attr *attr = field->u.perf_counter.attr;
+ struct lttng_perf_counter_field *perf_field =
+ container_of(nb, struct lttng_perf_counter_field, nb);
+ struct perf_event **events = perf_field->e;
+ struct perf_event_attr *attr = perf_field->attr;
struct perf_event *pevent;
struct perf_event *pevent;
- if (!field->u.perf_counter.hp_enable)
+ if (!perf_field->hp_enable)
return NOTIFY_OK;
switch (action) {
return NOTIFY_OK;
switch (action) {
struct lttng_ctx **ctx)
{
struct lttng_ctx_field *field;
struct lttng_ctx **ctx)
{
struct lttng_ctx_field *field;
+ struct lttng_perf_counter_field *perf_field;
struct perf_event **events;
struct perf_event_attr *attr;
int ret;
struct perf_event **events;
struct perf_event_attr *attr;
int ret;
if (!events)
return -ENOMEM;
if (!events)
return -ENOMEM;
- attr = kzalloc(sizeof(*field->u.perf_counter.attr), GFP_KERNEL);
+ attr = kzalloc(sizeof(struct perf_event_attr), GFP_KERNEL);
if (!attr) {
ret = -ENOMEM;
goto error_attr;
if (!attr) {
ret = -ENOMEM;
goto error_attr;
attr->pinned = 1;
attr->disabled = 0;
attr->pinned = 1;
attr->disabled = 0;
+ perf_field = kzalloc(sizeof(struct lttng_perf_counter_field), GFP_KERNEL);
+ if (!perf_field) {
+ ret = -ENOMEM;
+ goto error_alloc_perf_field;
+ }
+ perf_field->e = events;
+ perf_field->attr = attr;
+
name_alloc = kstrdup(name, GFP_KERNEL);
if (!name_alloc) {
ret = -ENOMEM;
name_alloc = kstrdup(name, GFP_KERNEL);
if (!name_alloc) {
ret = -ENOMEM;
ret = -ENOMEM;
goto append_context_error;
}
ret = -ENOMEM;
goto append_context_error;
}
- if (lttng_find_context(*ctx, name_alloc)) {
+ if (lttng_find_context(*ctx, name_alloc)) {
ret = -EEXIST;
goto find_error;
}
#ifdef CONFIG_HOTPLUG_CPU
ret = -EEXIST;
goto find_error;
}
#ifdef CONFIG_HOTPLUG_CPU
- field->u.perf_counter.nb.notifier_call =
+ perf_field->nb.notifier_call =
lttng_perf_counter_cpu_hp_callback;
lttng_perf_counter_cpu_hp_callback;
- field->u.perf_counter.nb.priority = 0;
- register_cpu_notifier(&field->u.perf_counter.nb);
+ perf_field->nb.priority = 0;
+ register_cpu_notifier(&perf_field->nb);
#endif
get_online_cpus();
#endif
get_online_cpus();
field->event_field.type.u.basic.integer.encoding = lttng_encode_none;
field->get_size = perf_counter_get_size;
field->record = perf_counter_record;
field->event_field.type.u.basic.integer.encoding = lttng_encode_none;
field->get_size = perf_counter_get_size;
field->record = perf_counter_record;
- field->u.perf_counter.e = events;
- field->u.perf_counter.attr = attr;
- field->u.perf_counter.hp_enable = 1;
+ field->u.perf_counter = perf_field;
+ perf_field->hp_enable = 1;
wrapper_vmalloc_sync_all();
return 0;
wrapper_vmalloc_sync_all();
return 0;
}
put_online_cpus();
#ifdef CONFIG_HOTPLUG_CPU
}
put_online_cpus();
#ifdef CONFIG_HOTPLUG_CPU
- unregister_cpu_notifier(&field->u.perf_counter.nb);
+ unregister_cpu_notifier(&perf_field->nb);
#endif
find_error:
lttng_remove_context_field(ctx, field);
append_context_error:
kfree(name_alloc);
name_alloc_error:
#endif
find_error:
lttng_remove_context_field(ctx, field);
append_context_error:
kfree(name_alloc);
name_alloc_error:
+ kfree(perf_field);
+error_alloc_perf_field:
kfree(attr);
error_attr:
kfree(events);
kfree(attr);
error_attr:
kfree(events);