coresight: etm-perf: configuring filters from perf core
authorMathieu Poirier <mathieu.poirier@linaro.org>
Thu, 25 Aug 2016 21:19:12 +0000 (15:19 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 31 Aug 2016 11:05:43 +0000 (13:05 +0200)
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>
drivers/hwtracing/coresight/coresight-etm-perf.c
drivers/hwtracing/coresight/coresight-etm-perf.h

index e4754e8..98eb207 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;
@@ -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)
index 87f5a13..3ffc9fe 100644 (file)
 #ifndef _CORESIGHT_ETM_PERF_H
 #define _CORESIGHT_ETM_PERF_H
 
+#include "coresight-priv.h"
+
 struct coresight_device;
 
+/*
+ * In both ETMv3 and v4 the maximum number of address comparator implentable
+ * is 8.  The actual number is implementation specific and will be checked
+ * when filters are applied.
+ */
+#define ETM_ADDR_CMP_MAX       8
+
+/**
+ * struct etm_filter - single instruction range or start/stop configuration.
+ * @start_addr:        The address to start tracing on.
+ * @stop_addr: The address to stop tracing on.
+ * @type:      Is this a range or start/stop filter.
+ */
+struct etm_filter {
+       unsigned long start_addr;
+       unsigned long stop_addr;
+       enum etm_addr_type type;
+};
+
+/**
+ * struct etm_filters - set of filters for a session
+ * @etm_filter:        All the filters for this session.
+ * @nr_filters:        Number of filters
+ * @ssstatus:  Status of the start/stop logic.
+ */
+struct etm_filters {
+       struct etm_filter       etm_filter[ETM_ADDR_CMP_MAX];
+       unsigned int            nr_filters;
+       bool                    ssstatus;
+};
+
+
 #ifdef CONFIG_CORESIGHT
 int etm_perf_symlink(struct coresight_device *csdev, bool link);