aboutsummaryrefslogtreecommitdiff
path: root/drivers/hwtracing/coresight/coresight-trbe.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwtracing/coresight/coresight-trbe.c')
-rw-r--r--drivers/hwtracing/coresight/coresight-trbe.c125
1 files changed, 92 insertions, 33 deletions
diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c
index 276862c07e32..2b386bb848f8 100644
--- a/drivers/hwtracing/coresight/coresight-trbe.c
+++ b/drivers/hwtracing/coresight/coresight-trbe.c
@@ -91,10 +91,16 @@ struct trbe_buf {
*/
#define TRBE_WORKAROUND_OVERWRITE_FILL_MODE 0
#define TRBE_WORKAROUND_WRITE_OUT_OF_RANGE 1
+#define TRBE_NEEDS_DRAIN_AFTER_DISABLE 2
+#define TRBE_NEEDS_CTXT_SYNC_AFTER_ENABLE 3
+#define TRBE_IS_BROKEN 4
static int trbe_errata_cpucaps[] = {
[TRBE_WORKAROUND_OVERWRITE_FILL_MODE] = ARM64_WORKAROUND_TRBE_OVERWRITE_FILL_MODE,
[TRBE_WORKAROUND_WRITE_OUT_OF_RANGE] = ARM64_WORKAROUND_TRBE_WRITE_OUT_OF_RANGE,
+ [TRBE_NEEDS_DRAIN_AFTER_DISABLE] = ARM64_WORKAROUND_2064142,
+ [TRBE_NEEDS_CTXT_SYNC_AFTER_ENABLE] = ARM64_WORKAROUND_2038923,
+ [TRBE_IS_BROKEN] = ARM64_WORKAROUND_1902691,
-1, /* Sentinel, must be the last entry */
};
@@ -167,6 +173,32 @@ static inline bool trbe_may_write_out_of_range(struct trbe_cpudata *cpudata)
return trbe_has_erratum(cpudata, TRBE_WORKAROUND_WRITE_OUT_OF_RANGE);
}
+static inline bool trbe_needs_drain_after_disable(struct trbe_cpudata *cpudata)
+{
+ /*
+ * Errata affected TRBE implementation will need TSB CSYNC and
+ * DSB in order to prevent subsequent writes into certain TRBE
+ * system registers from being ignored and not effected.
+ */
+ return trbe_has_erratum(cpudata, TRBE_NEEDS_DRAIN_AFTER_DISABLE);
+}
+
+static inline bool trbe_needs_ctxt_sync_after_enable(struct trbe_cpudata *cpudata)
+{
+ /*
+ * Errata affected TRBE implementation will need an additional
+ * context synchronization in order to prevent an inconsistent
+ * TRBE prohibited region view on the CPU which could possibly
+ * corrupt the TRBE buffer or the TRBE state.
+ */
+ return trbe_has_erratum(cpudata, TRBE_NEEDS_CTXT_SYNC_AFTER_ENABLE);
+}
+
+static inline bool trbe_is_broken(struct trbe_cpudata *cpudata)
+{
+ return trbe_has_erratum(cpudata, TRBE_IS_BROKEN);
+}
+
static int trbe_alloc_node(struct perf_event *event)
{
if (event->cpu == -1)
@@ -174,17 +206,31 @@ static int trbe_alloc_node(struct perf_event *event)
return cpu_to_node(event->cpu);
}
-static void trbe_drain_buffer(void)
+static inline void trbe_drain_buffer(void)
{
tsb_csync();
dsb(nsh);
}
-static void trbe_drain_and_disable_local(void)
+static inline void set_trbe_enabled(struct trbe_cpudata *cpudata, u64 trblimitr)
{
- u64 trblimitr = read_sysreg_s(SYS_TRBLIMITR_EL1);
+ /*
+ * Enable the TRBE without clearing LIMITPTR which
+ * might be required for fetching the buffer limits.
+ */
+ trblimitr |= TRBLIMITR_ENABLE;
+ write_sysreg_s(trblimitr, SYS_TRBLIMITR_EL1);
- trbe_drain_buffer();
+ /* Synchronize the TRBE enable event */
+ isb();
+
+ if (trbe_needs_ctxt_sync_after_enable(cpudata))
+ isb();
+}
+
+static inline void set_trbe_disabled(struct trbe_cpudata *cpudata)
+{
+ u64 trblimitr = read_sysreg_s(SYS_TRBLIMITR_EL1);
/*
* Disable the TRBE without clearing LIMITPTR which
@@ -192,12 +238,21 @@ static void trbe_drain_and_disable_local(void)
*/
trblimitr &= ~TRBLIMITR_ENABLE;
write_sysreg_s(trblimitr, SYS_TRBLIMITR_EL1);
+
+ if (trbe_needs_drain_after_disable(cpudata))
+ trbe_drain_buffer();
isb();
}
-static void trbe_reset_local(void)
+static void trbe_drain_and_disable_local(struct trbe_cpudata *cpudata)
{
- trbe_drain_and_disable_local();
+ trbe_drain_buffer();
+ set_trbe_disabled(cpudata);
+}
+
+static void trbe_reset_local(struct trbe_cpudata *cpudata)
+{
+ trbe_drain_and_disable_local(cpudata);
write_sysreg_s(0, SYS_TRBLIMITR_EL1);
write_sysreg_s(0, SYS_TRBPTR_EL1);
write_sysreg_s(0, SYS_TRBBASER_EL1);
@@ -234,7 +289,7 @@ static void trbe_stop_and_truncate_event(struct perf_output_handle *handle)
* at event_stop(). So disable the TRBE here and leave
* the update_buffer() to return a 0 size.
*/
- trbe_drain_and_disable_local();
+ trbe_drain_and_disable_local(buf->cpudata);
perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
perf_aux_output_end(handle, 0);
*this_cpu_ptr(buf->cpudata->drvdata->handle) = NULL;
@@ -536,9 +591,10 @@ static void clr_trbe_status(void)
write_sysreg_s(trbsr, SYS_TRBSR_EL1);
}
-static void set_trbe_limit_pointer_enabled(unsigned long addr)
+static void set_trbe_limit_pointer_enabled(struct trbe_buf *buf)
{
u64 trblimitr = read_sysreg_s(SYS_TRBLIMITR_EL1);
+ unsigned long addr = buf->trbe_limit;
WARN_ON(!IS_ALIGNED(addr, (1UL << TRBLIMITR_LIMIT_SHIFT)));
WARN_ON(!IS_ALIGNED(addr, PAGE_SIZE));
@@ -566,12 +622,7 @@ static void set_trbe_limit_pointer_enabled(unsigned long addr)
trblimitr |= (TRBE_TRIG_MODE_IGNORE & TRBLIMITR_TRIG_MODE_MASK) <<
TRBLIMITR_TRIG_MODE_SHIFT;
trblimitr |= (addr & PAGE_MASK);
-
- trblimitr |= TRBLIMITR_ENABLE;
- write_sysreg_s(trblimitr, SYS_TRBLIMITR_EL1);
-
- /* Synchronize the TRBE enable event */
- isb();
+ set_trbe_enabled(buf->cpudata, trblimitr);
}
static void trbe_enable_hw(struct trbe_buf *buf)
@@ -579,8 +630,7 @@ static void trbe_enable_hw(struct trbe_buf *buf)
WARN_ON(buf->trbe_hw_base < buf->trbe_base);
WARN_ON(buf->trbe_write < buf->trbe_hw_base);
WARN_ON(buf->trbe_write >= buf->trbe_limit);
- set_trbe_disabled();
- isb();
+ set_trbe_disabled(buf->cpudata);
clr_trbe_status();
set_trbe_base_pointer(buf->trbe_hw_base);
set_trbe_write_pointer(buf->trbe_write);
@@ -590,7 +640,7 @@ static void trbe_enable_hw(struct trbe_buf *buf)
* till now before enabling the TRBE.
*/
isb();
- set_trbe_limit_pointer_enabled(buf->trbe_limit);
+ set_trbe_limit_pointer_enabled(buf);
}
static enum trbe_fault_action trbe_get_fault_act(struct perf_output_handle *handle,
@@ -775,7 +825,7 @@ static unsigned long arm_trbe_update_buffer(struct coresight_device *csdev,
* the TRBE here will ensure that no IRQ could be generated when the perf
* handle gets freed in etm_event_stop().
*/
- trbe_drain_and_disable_local();
+ trbe_drain_and_disable_local(cpudata);
/* Check if there is a pending interrupt and handle it here */
status = read_sysreg_s(SYS_TRBSR_EL1);
@@ -986,7 +1036,7 @@ static int arm_trbe_disable(struct coresight_device *csdev)
if (cpudata->mode != CS_MODE_PERF)
return -EINVAL;
- trbe_drain_and_disable_local();
+ trbe_drain_and_disable_local(cpudata);
buf->cpudata = NULL;
cpudata->buf = NULL;
cpudata->mode = CS_MODE_DISABLED;
@@ -995,16 +1045,15 @@ static int arm_trbe_disable(struct coresight_device *csdev)
static void trbe_handle_spurious(struct perf_output_handle *handle)
{
- u64 limitr = read_sysreg_s(SYS_TRBLIMITR_EL1);
+ struct trbe_buf *buf = etm_perf_sink_config(handle);
+ u64 trblimitr = read_sysreg_s(SYS_TRBLIMITR_EL1);
/*
* If the IRQ was spurious, simply re-enable the TRBE
* back without modifying the buffer parameters to
* retain the trace collected so far.
*/
- limitr |= TRBLIMITR_ENABLE;
- write_sysreg_s(limitr, SYS_TRBLIMITR_EL1);
- isb();
+ set_trbe_enabled(buf->cpudata, trblimitr);
}
static int trbe_handle_overflow(struct perf_output_handle *handle)
@@ -1028,7 +1077,7 @@ static int trbe_handle_overflow(struct perf_output_handle *handle)
* is able to detect this with a disconnected handle
* (handle->event = NULL).
*/
- trbe_drain_and_disable_local();
+ trbe_drain_and_disable_local(buf->cpudata);
*this_cpu_ptr(buf->cpudata->drvdata->handle) = NULL;
return -EINVAL;
}
@@ -1062,6 +1111,7 @@ static irqreturn_t arm_trbe_irq_handler(int irq, void *dev)
{
struct perf_output_handle **handle_ptr = dev;
struct perf_output_handle *handle = *handle_ptr;
+ struct trbe_buf *buf = etm_perf_sink_config(handle);
enum trbe_fault_action act;
u64 status;
bool truncated = false;
@@ -1082,7 +1132,7 @@ static irqreturn_t arm_trbe_irq_handler(int irq, void *dev)
* Ensure the trace is visible to the CPUs and
* any external aborts have been resolved.
*/
- trbe_drain_and_disable_local();
+ trbe_drain_and_disable_local(buf->cpudata);
clr_trbe_irq();
isb();
@@ -1167,8 +1217,9 @@ static const struct attribute_group *arm_trbe_groups[] = {
static void arm_trbe_enable_cpu(void *info)
{
struct trbe_drvdata *drvdata = info;
+ struct trbe_cpudata *cpudata = this_cpu_ptr(drvdata->cpudata);
- trbe_reset_local();
+ trbe_reset_local(cpudata);
enable_percpu_irq(drvdata->irq, IRQ_TYPE_NONE);
}
@@ -1244,6 +1295,11 @@ static void arm_trbe_probe_cpu(void *info)
*/
trbe_check_errata(cpudata);
+ if (trbe_is_broken(cpudata)) {
+ pr_err("Disabling TRBE on cpu%d due to erratum\n", cpu);
+ goto cpu_clear;
+ }
+
/*
* If the TRBE is affected by erratum TRBE_WORKAROUND_OVERWRITE_FILL_MODE,
* we must always program the TBRPTR_EL1, 256bytes from a page
@@ -1276,7 +1332,7 @@ static void arm_trbe_remove_coresight_cpu(void *info)
struct coresight_device *trbe_csdev = coresight_get_percpu_sink(cpu);
disable_percpu_irq(drvdata->irq);
- trbe_reset_local();
+ trbe_reset_local(cpudata);
if (trbe_csdev) {
coresight_unregister(trbe_csdev);
cpudata->drvdata = NULL;
@@ -1349,8 +1405,10 @@ static int arm_trbe_cpu_teardown(unsigned int cpu, struct hlist_node *node)
struct trbe_drvdata *drvdata = hlist_entry_safe(node, struct trbe_drvdata, hotplug_node);
if (cpumask_test_cpu(cpu, &drvdata->supported_cpus)) {
+ struct trbe_cpudata *cpudata = per_cpu_ptr(drvdata->cpudata, cpu);
+
disable_percpu_irq(drvdata->irq);
- trbe_reset_local();
+ trbe_reset_local(cpudata);
}
return 0;
}
@@ -1423,6 +1481,12 @@ static int arm_trbe_device_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
int ret;
+ /* Trace capture is not possible with kernel page table isolation */
+ if (arm64_kernel_unmapped_at_el0()) {
+ pr_err("TRBE wouldn't work if kernel gets unmapped at EL0\n");
+ return -EOPNOTSUPP;
+ }
+
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
@@ -1484,11 +1548,6 @@ static int __init arm_trbe_init(void)
{
int ret;
- if (arm64_kernel_unmapped_at_el0()) {
- pr_err("TRBE wouldn't work if kernel gets unmapped at EL0\n");
- return -EOPNOTSUPP;
- }
-
ret = platform_driver_register(&arm_trbe_driver);
if (!ret)
return 0;