diff options
Diffstat (limited to 'drivers/hwtracing/coresight/coresight-trbe.c')
-rw-r--r-- | drivers/hwtracing/coresight/coresight-trbe.c | 125 |
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; |