|
@@ -27,6 +27,7 @@
|
|
#include <linux/types.h>
|
|
#include <linux/types.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
+#include "coresight-etm-perf.h"
|
|
#include "coresight-priv.h"
|
|
#include "coresight-priv.h"
|
|
|
|
|
|
static struct pmu etm_pmu;
|
|
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 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;
|
|
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)
|
|
static void free_event_data(struct work_struct *work)
|
|
{
|
|
{
|
|
int cpu;
|
|
int cpu;
|
|
@@ -342,6 +377,87 @@ static void etm_event_del(struct perf_event *event, int mode)
|
|
etm_event_stop(event, PERF_EF_UPDATE);
|
|
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)
|
|
int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
|
{
|
|
{
|
|
char entry[sizeof("cpu9999999")];
|
|
char entry[sizeof("cpu9999999")];
|
|
@@ -371,18 +487,21 @@ static int __init etm_perf_init(void)
|
|
{
|
|
{
|
|
int ret;
|
|
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);
|
|
ret = perf_pmu_register(&etm_pmu, CORESIGHT_ETM_PMU_NAME, -1);
|
|
if (ret == 0)
|
|
if (ret == 0)
|