diff options
author | Mathieu Poirier <mathieu.poirier@linaro.org> | 2016-08-26 00:19:12 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-08-31 14:05:43 +0300 |
commit | ca878b14660c340287fb17d70950cfc09c6d698c (patch) | |
tree | ef249164f70275d7a10f00f274318819c2ecae16 /drivers/hwtracing/coresight/coresight-etm-perf.c | |
parent | 2b7adc460ff8bff95f79c03451448895e761c4a6 (diff) | |
download | linux-ca878b14660c340287fb17d70950cfc09c6d698c.tar.xz |
coresight: etm-perf: configuring filters from perf core
This patch implements the required API needed to access
and retrieve range and start/stop filters from the perf core.
Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/hwtracing/coresight/coresight-etm-perf.c')
-rw-r--r-- | drivers/hwtracing/coresight/coresight-etm-perf.c | 149 |
1 files changed, 134 insertions, 15 deletions
diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index e4754e8e6906..98eb207684fa 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -27,6 +27,7 @@ #include <linux/types.h> #include <linux/workqueue.h> +#include "coresight-etm-perf.h" #include "coresight-priv.h" static struct pmu etm_pmu; @@ -71,14 +72,48 @@ static const struct attribute_group *etm_pmu_attr_groups[] = { static void etm_event_read(struct perf_event *event) {} -static int etm_event_init(struct perf_event *event) +static int etm_addr_filters_alloc(struct perf_event *event) { - if (event->attr.type != etm_pmu.type) - return -ENOENT; + struct etm_filters *filters; + int node = event->cpu == -1 ? -1 : cpu_to_node(event->cpu); + + filters = kzalloc_node(sizeof(struct etm_filters), GFP_KERNEL, node); + if (!filters) + return -ENOMEM; + + if (event->parent) + memcpy(filters, event->parent->hw.addr_filters, + sizeof(*filters)); + + event->hw.addr_filters = filters; return 0; } +static void etm_event_destroy(struct perf_event *event) +{ + kfree(event->hw.addr_filters); + event->hw.addr_filters = NULL; +} + +static int etm_event_init(struct perf_event *event) +{ + int ret = 0; + + if (event->attr.type != etm_pmu.type) { + ret = -ENOENT; + goto out; + } + + ret = etm_addr_filters_alloc(event); + if (ret) + goto out; + + event->destroy = etm_event_destroy; +out: + return ret; +} + static void free_event_data(struct work_struct *work) { int cpu; @@ -342,6 +377,87 @@ static void etm_event_del(struct perf_event *event, int mode) etm_event_stop(event, PERF_EF_UPDATE); } +static int etm_addr_filters_validate(struct list_head *filters) +{ + bool range = false, address = false; + int index = 0; + struct perf_addr_filter *filter; + + list_for_each_entry(filter, filters, entry) { + /* + * No need to go further if there's no more + * room for filters. + */ + if (++index > ETM_ADDR_CMP_MAX) + return -EOPNOTSUPP; + + /* + * As taken from the struct perf_addr_filter documentation: + * @range: 1: range, 0: address + * + * At this time we don't allow range and start/stop filtering + * to cohabitate, they have to be mutually exclusive. + */ + if ((filter->range == 1) && address) + return -EOPNOTSUPP; + + if ((filter->range == 0) && range) + return -EOPNOTSUPP; + + /* + * For range filtering, the second address in the address + * range comparator needs to be higher than the first. + * Invalid otherwise. + */ + if (filter->range && filter->size == 0) + return -EINVAL; + + /* + * Everything checks out with this filter, record what we've + * received before moving on to the next one. + */ + if (filter->range) + range = true; + else + address = true; + } + + return 0; +} + +static void etm_addr_filters_sync(struct perf_event *event) +{ + struct perf_addr_filters_head *head = perf_event_addr_filters(event); + unsigned long start, stop, *offs = event->addr_filters_offs; + struct etm_filters *filters = event->hw.addr_filters; + struct etm_filter *etm_filter; + struct perf_addr_filter *filter; + int i = 0; + + list_for_each_entry(filter, &head->list, entry) { + start = filter->offset + offs[i]; + stop = start + filter->size; + etm_filter = &filters->etm_filter[i]; + + if (filter->range == 1) { + etm_filter->start_addr = start; + etm_filter->stop_addr = stop; + etm_filter->type = ETM_ADDR_TYPE_RANGE; + } else { + if (filter->filter == 1) { + etm_filter->start_addr = start; + etm_filter->type = ETM_ADDR_TYPE_START; + } else { + etm_filter->stop_addr = stop; + etm_filter->type = ETM_ADDR_TYPE_STOP; + } + } + i++; + } + + filters->nr_filters = i; +} + int etm_perf_symlink(struct coresight_device *csdev, bool link) { char entry[sizeof("cpu9999999")]; @@ -371,18 +487,21 @@ static int __init etm_perf_init(void) { int ret; - etm_pmu.capabilities = PERF_PMU_CAP_EXCLUSIVE; - - etm_pmu.attr_groups = etm_pmu_attr_groups; - etm_pmu.task_ctx_nr = perf_sw_context; - etm_pmu.read = etm_event_read; - etm_pmu.event_init = etm_event_init; - etm_pmu.setup_aux = etm_setup_aux; - etm_pmu.free_aux = etm_free_aux; - etm_pmu.start = etm_event_start; - etm_pmu.stop = etm_event_stop; - etm_pmu.add = etm_event_add; - etm_pmu.del = etm_event_del; + etm_pmu.capabilities = PERF_PMU_CAP_EXCLUSIVE; + + etm_pmu.attr_groups = etm_pmu_attr_groups; + etm_pmu.task_ctx_nr = perf_sw_context; + etm_pmu.read = etm_event_read; + etm_pmu.event_init = etm_event_init; + etm_pmu.setup_aux = etm_setup_aux; + etm_pmu.free_aux = etm_free_aux; + etm_pmu.start = etm_event_start; + etm_pmu.stop = etm_event_stop; + etm_pmu.add = etm_event_add; + etm_pmu.del = etm_event_del; + etm_pmu.addr_filters_sync = etm_addr_filters_sync; + etm_pmu.addr_filters_validate = etm_addr_filters_validate; + etm_pmu.nr_addr_filters = ETM_ADDR_CMP_MAX; ret = perf_pmu_register(&etm_pmu, CORESIGHT_ETM_PMU_NAME, -1); if (ret == 0) |