coresight: etm3x: implementing perf_enable/disable() API
[firefly-linux-kernel-4.4.55.git] / drivers / hwtracing / coresight / coresight-etm3x.c
index 1952dc31fee41c154299ec03ae2f6c95cb632c15..a9b820ec16aab4b45d4160d8f97d8ba56b80c54e 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/seq_file.h>
 #include <linux/uaccess.h>
 #include <linux/clk.h>
+#include <linux/perf_event.h>
 #include <asm/sections.h>
 
 #include "coresight-etm.h"
@@ -41,7 +42,6 @@ module_param_named(boot_enable, boot_enable, int, S_IRUGO);
 /* The number of ETM/PTM currently registered */
 static int etm_count;
 static struct etm_drvdata *etmdrvdata[NR_CPUS];
-static void etm_init_default_data(struct etm_config *config);
 
 /*
  * Memory mapped writes to clear os lock are not supported on some processors
@@ -194,6 +194,19 @@ void etm_set_default(struct etm_config *config)
        if (WARN_ON_ONCE(!config))
                return;
 
+       /*
+        * Taken verbatim from the TRM:
+        *
+        * To trace all memory:
+        *  set bit [24] in register 0x009, the ETMTECR1, to 1
+        *  set all other bits in register 0x009, the ETMTECR1, to 0
+        *  set all bits in register 0x007, the ETMTECR2, to 0
+        *  set register 0x008, the ETMTEEVR, to 0x6F (TRUE).
+        */
+       config->enable_ctrl1 = BIT(24);
+       config->enable_ctrl2 = 0x0;
+       config->enable_event = ETM_HARD_WIRE_RES_A;
+
        config->trigger_event = ETM_DEFAULT_EVENT_VAL;
        config->enable_event = ETM_HARD_WIRE_RES_A;
 
@@ -222,6 +235,110 @@ void etm_set_default(struct etm_config *config)
        config->ctxid_mask = 0x0;
 }
 
+void etm_config_trace_mode(struct etm_config *config)
+{
+       u32 flags, mode;
+
+       mode = config->mode;
+
+       mode &= (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER);
+
+       /* excluding kernel AND user space doesn't make sense */
+       if (mode == (ETM_MODE_EXCL_KERN | ETM_MODE_EXCL_USER))
+               return;
+
+       /* nothing to do if neither flags are set */
+       if (!(mode & ETM_MODE_EXCL_KERN) && !(mode & ETM_MODE_EXCL_USER))
+               return;
+
+       flags = (1 << 0 |       /* instruction execute */
+                3 << 3 |       /* ARM instruction */
+                0 << 5 |       /* No data value comparison */
+                0 << 7 |       /* No exact mach */
+                0 << 8);       /* Ignore context ID */
+
+       /* No need to worry about single address comparators. */
+       config->enable_ctrl2 = 0x0;
+
+       /* Bit 0 is address range comparator 1 */
+       config->enable_ctrl1 = ETMTECR1_ADDR_COMP_1;
+
+       /*
+        * On ETMv3.5:
+        * ETMACTRn[13,11] == Non-secure state comparison control
+        * ETMACTRn[12,10] == Secure state comparison control
+        *
+        * b00 == Match in all modes in this state
+        * b01 == Do not match in any more in this state
+        * b10 == Match in all modes excepts user mode in this state
+        * b11 == Match only in user mode in this state
+        */
+
+       /* Tracing in secure mode is not supported at this time */
+       flags |= (0 << 12 | 1 << 10);
+
+       if (mode & ETM_MODE_EXCL_USER) {
+               /* exclude user, match all modes except user mode */
+               flags |= (1 << 13 | 0 << 11);
+       } else {
+               /* exclude kernel, match only in user mode */
+               flags |= (1 << 13 | 1 << 11);
+       }
+
+       /*
+        * The ETMEEVR register is already set to "hard wire A".  As such
+        * all there is to do is setup an address comparator that spans
+        * the entire address range and configure the state and mode bits.
+        */
+       config->addr_val[0] = (u32) 0x0;
+       config->addr_val[1] = (u32) ~0x0;
+       config->addr_acctype[0] = flags;
+       config->addr_acctype[1] = flags;
+       config->addr_type[0] = ETM_ADDR_TYPE_RANGE;
+       config->addr_type[1] = ETM_ADDR_TYPE_RANGE;
+}
+
+#define ETM3X_SUPPORTED_OPTIONS (ETMCR_CYC_ACC | ETMCR_TIMESTAMP_EN)
+
+static int etm_parse_event_config(struct etm_drvdata *drvdata,
+                                 struct perf_event_attr *attr)
+{
+       struct etm_config *config = &drvdata->config;
+
+       if (!attr)
+               return -EINVAL;
+
+       /* Clear configuration from previous run */
+       memset(config, 0, sizeof(struct etm_config));
+
+       if (attr->exclude_kernel)
+               config->mode = ETM_MODE_EXCL_KERN;
+
+       if (attr->exclude_user)
+               config->mode = ETM_MODE_EXCL_USER;
+
+       /* Always start from the default config */
+       etm_set_default(config);
+
+       /*
+        * By default the tracers are configured to trace the whole address
+        * range.  Narrow the field only if requested by user space.
+        */
+       if (config->mode)
+               etm_config_trace_mode(config);
+
+       /*
+        * At this time only cycle accurate and timestamp options are
+        * available.
+        */
+       if (attr->config & ~ETM3X_SUPPORTED_OPTIONS)
+               return -EINVAL;
+
+       config->ctrl = attr->config;
+
+       return 0;
+}
+
 static void etm_enable_hw(void *info)
 {
        int i;
@@ -241,8 +358,10 @@ static void etm_enable_hw(void *info)
        etm_set_prog(drvdata);
 
        etmcr = etm_readl(drvdata, ETMCR);
-       etmcr &= (ETMCR_PWD_DWN | ETMCR_ETM_PRG);
+       /* Clear setting from a previous run if need be */
+       etmcr &= ~ETM3X_SUPPORTED_OPTIONS;
        etmcr |= drvdata->port_size;
+       etmcr |= ETMCR_ETM_EN;
        etm_writel(drvdata, config->ctrl | etmcr, ETMCR);
        etm_writel(drvdata, config->trigger_event, ETMTRIGGER);
        etm_writel(drvdata, config->startstop_ctrl, ETMTSSCR);
@@ -282,9 +401,6 @@ static void etm_enable_hw(void *info)
        /* No VMID comparator value selected */
        etm_writel(drvdata, 0x0, ETMVMIDCVR);
 
-       /* Ensures trace output is enabled from this ETM */
-       etm_writel(drvdata, config->ctrl | ETMCR_ETM_EN | etmcr, ETMCR);
-
        etm_clr_prog(drvdata);
        CS_LOCK(drvdata->base);
 
@@ -306,7 +422,7 @@ int etm_get_trace_id(struct etm_drvdata *drvdata)
        if (!drvdata)
                goto out;
 
-       if (!drvdata->enable)
+       if (!local_read(&drvdata->mode))
                return drvdata->traceid;
 
        pm_runtime_get_sync(drvdata->dev);
@@ -332,7 +448,23 @@ static int etm_trace_id(struct coresight_device *csdev)
        return etm_get_trace_id(drvdata);
 }
 
-static int etm_enable(struct coresight_device *csdev)
+static int etm_enable_perf(struct coresight_device *csdev,
+                          struct perf_event_attr *attr)
+{
+       struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+       if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id()))
+               return -EINVAL;
+
+       /* Configure the tracer based on the session's specifics */
+       etm_parse_event_config(drvdata, attr);
+       /* And enable it */
+       etm_enable_hw(drvdata);
+
+       return 0;
+}
+
+static int etm_enable_sysfs(struct coresight_device *csdev)
 {
        struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
        int ret;
@@ -351,18 +483,48 @@ static int etm_enable(struct coresight_device *csdev)
                        goto err;
        }
 
-       drvdata->enable = true;
        drvdata->sticky_enable = true;
-
        spin_unlock(&drvdata->spinlock);
 
        dev_info(drvdata->dev, "ETM tracing enabled\n");
        return 0;
+
 err:
        spin_unlock(&drvdata->spinlock);
        return ret;
 }
 
+static int etm_enable(struct coresight_device *csdev,
+                     struct perf_event_attr *attr, u32 mode)
+{
+       int ret;
+       u32 val;
+       struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+       val = local_cmpxchg(&drvdata->mode, CS_MODE_DISABLED, mode);
+
+       /* Someone is already using the tracer */
+       if (val)
+               return -EBUSY;
+
+       switch (mode) {
+       case CS_MODE_SYSFS:
+               ret = etm_enable_sysfs(csdev);
+               break;
+       case CS_MODE_PERF:
+               ret = etm_enable_perf(csdev, attr);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       /* The tracer didn't start */
+       if (ret)
+               local_set(&drvdata->mode, CS_MODE_DISABLED);
+
+       return ret;
+}
+
 static void etm_disable_hw(void *info)
 {
        int i;
@@ -372,9 +534,6 @@ static void etm_disable_hw(void *info)
        CS_UNLOCK(drvdata->base);
        etm_set_prog(drvdata);
 
-       /* Program trace enable to low by using always false event */
-       etm_writel(drvdata, ETM_HARD_WIRE_RES_A | ETM_EVENT_NOT_A, ETMTEEVR);
-
        /* Read back sequencer and counters for post trace analysis */
        config->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK);
 
@@ -387,7 +546,28 @@ static void etm_disable_hw(void *info)
        dev_dbg(drvdata->dev, "cpu: %d disable smp call done\n", drvdata->cpu);
 }
 
-static void etm_disable(struct coresight_device *csdev)
+static void etm_disable_perf(struct coresight_device *csdev)
+{
+       struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+       if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id()))
+               return;
+
+       CS_UNLOCK(drvdata->base);
+
+       /* Setting the prog bit disables tracing immediately */
+       etm_set_prog(drvdata);
+
+       /*
+        * There is no way to know when the tracer will be used again so
+        * power down the tracer.
+        */
+       etm_set_pwrdwn(drvdata);
+
+       CS_LOCK(drvdata->base);
+}
+
+static void etm_disable_sysfs(struct coresight_device *csdev)
 {
        struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
 
@@ -405,7 +585,6 @@ static void etm_disable(struct coresight_device *csdev)
         * ensures that register writes occur when cpu is powered.
         */
        smp_call_function_single(drvdata->cpu, etm_disable_hw, drvdata, 1);
-       drvdata->enable = false;
 
        spin_unlock(&drvdata->spinlock);
        put_online_cpus();
@@ -413,6 +592,36 @@ static void etm_disable(struct coresight_device *csdev)
        dev_info(drvdata->dev, "ETM tracing disabled\n");
 }
 
+static void etm_disable(struct coresight_device *csdev)
+{
+       u32 mode;
+       struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+
+       /*
+        * For as long as the tracer isn't disabled another entity can't
+        * change its status.  As such we can read the status here without
+        * fearing it will change under us.
+        */
+       mode = local_read(&drvdata->mode);
+
+       switch (mode) {
+       case CS_MODE_DISABLED:
+               break;
+       case CS_MODE_SYSFS:
+               etm_disable_sysfs(csdev);
+               break;
+       case CS_MODE_PERF:
+               etm_disable_perf(csdev);
+               break;
+       default:
+               WARN_ON_ONCE(mode);
+               return;
+       }
+
+       if (mode)
+               local_set(&drvdata->mode, CS_MODE_DISABLED);
+}
+
 static const struct coresight_ops_source etm_source_ops = {
        .cpu_id         = etm_cpu_id,
        .trace_id       = etm_trace_id,
@@ -440,7 +649,7 @@ static int etm_cpu_callback(struct notifier_block *nfb, unsigned long action,
                        etmdrvdata[cpu]->os_unlock = true;
                }
 
-               if (etmdrvdata[cpu]->enable)
+               if (local_read(&etmdrvdata[cpu]->mode))
                        etm_enable_hw(etmdrvdata[cpu]);
                spin_unlock(&etmdrvdata[cpu]->spinlock);
                break;
@@ -453,7 +662,7 @@ static int etm_cpu_callback(struct notifier_block *nfb, unsigned long action,
 
        case CPU_DYING:
                spin_lock(&etmdrvdata[cpu]->spinlock);
-               if (etmdrvdata[cpu]->enable)
+               if (local_read(&etmdrvdata[cpu]->mode))
                        etm_disable_hw(etmdrvdata[cpu]);
                spin_unlock(&etmdrvdata[cpu]->spinlock);
                break;
@@ -528,30 +737,6 @@ static void etm_init_arch_data(void *info)
        CS_LOCK(drvdata->base);
 }
 
-static void etm_init_default_data(struct etm_config *config)
-{
-       u32 flags = (1 << 0 | /* instruction execute*/
-                    3 << 3 | /* ARM instruction */
-                    0 << 5 | /* No data value comparison */
-                    0 << 7 | /* No exact mach */
-                    0 << 8 | /* Ignore context ID */
-                    0 << 10); /* Security ignored */
-
-       if (WARN_ON_ONCE(!config))
-               return;
-
-       config->ctrl = (ETMCR_CYC_ACC | ETMCR_TIMESTAMP_EN);
-       config->enable_ctrl1 = ETMTECR1_ADDR_COMP_1;
-       config->addr_val[0] = (u32) _stext;
-       config->addr_val[1] = (u32) _etext;
-       config->addr_acctype[0] = flags;
-       config->addr_acctype[1] = flags;
-       config->addr_type[0] = ETM_ADDR_TYPE_RANGE;
-       config->addr_type[1] = ETM_ADDR_TYPE_RANGE;
-
-       etm_set_default(config);
-}
-
 static void etm_init_trace_id(struct etm_drvdata *drvdata)
 {
        /*
@@ -628,7 +813,7 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
        }
 
        etm_init_trace_id(drvdata);
-       etm_init_default_data(&drvdata->config);
+       etm_set_default(&drvdata->config);
 
        desc->type = CORESIGHT_DEV_TYPE_SOURCE;
        desc->subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC;