Merge remote-tracking branch 'char-misc/char-misc-next'
[deliverable/linux.git] / drivers / hwtracing / coresight / coresight-etm-perf.c
index 755125f7917f2cb6aa369fd88d28d763e688a9d6..2cd7c718198a82e3a2aeecc5aed1c3a5643157d7 100644 (file)
@@ -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;
@@ -100,7 +135,7 @@ static void free_event_data(struct work_struct *work)
        }
 
        for_each_cpu(cpu, mask) {
-               if (event_data->path[cpu])
+               if (!(IS_ERR_OR_NULL(event_data->path[cpu])))
                        coresight_release_path(event_data->path[cpu]);
        }
 
@@ -185,7 +220,7 @@ static void *etm_setup_aux(int event_cpu, void **pages,
                 * referenced later when the path is actually needed.
                 */
                event_data->path[cpu] = coresight_build_path(csdev);
-               if (!event_data->path[cpu])
+               if (IS_ERR(event_data->path[cpu]))
                        goto err;
        }
 
@@ -258,7 +293,7 @@ static void etm_event_start(struct perf_event *event, int flags)
        event->hw.state = 0;
 
        /* Finally enable the tracer */
-       if (source_ops(csdev)->enable(csdev, &event->attr, CS_MODE_PERF))
+       if (source_ops(csdev)->enable(csdev, event, CS_MODE_PERF))
                goto fail_end_stop;
 
 out:
@@ -291,7 +326,7 @@ static void etm_event_stop(struct perf_event *event, int mode)
                return;
 
        /* stop tracer */
-       source_ops(csdev)->disable(csdev);
+       source_ops(csdev)->disable(csdev, event);
 
        /* tell the core */
        event->hw.state = PERF_HES_STOPPED;
@@ -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)
This page took 0.026826 seconds and 5 git commands to generate.