[PATCH 11/25] coresight: trbe: Use circular buffer mode for snapshots
James Clark
james.clark at linaro.org
Mon Oct 19 03:13:17 PDT 2026
On 15/09/2026 16:48, Leo Yan wrote:
> TRBE uses Fill mode for snapshots, stopping trace collection and raising
> a maintenance interrupt at each wrap. Circular buffer mode retains the
> most recent trace without stopping collection or generating wrap
> interrupts.
>
> Use circular buffer mode for snapshots unless the CPU requires the write
> out-of-range workaround. Keep Fill mode and its guard page on affected
> CPUs. Select the mode on each enable to handle task migration.
>
> Calculate the minimum known trace advance from the write pointer and the
> WRAP flag, since additional complete laps cannot be counted. Advance the
> AUX head by this amount, then clamp the reported size to one buffer of
> retained trace.
>
> Assisted-by: Codex:gpt-6
> Signed-off-by: Leo Yan <leo.yan at arm.com>
> ---
> drivers/hwtracing/coresight/coresight-trbe.c | 61 +++++++++++++++++++++-------
> 1 file changed, 47 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c
> index a31f2ebc327cd681344a62de28c0b3840505a9cb..ab3c5c7d27f3d272b07eade1e6452559808b196c 100644
> --- a/drivers/hwtracing/coresight/coresight-trbe.c
> +++ b/drivers/hwtracing/coresight/coresight-trbe.c
> @@ -69,6 +69,7 @@ struct trbe_buf {
> int nr_pages;
> void **pages;
> bool snapshot;
> + bool circular;
> struct trbe_cpudata *cpudata;
> };
>
> @@ -324,9 +325,11 @@ static void trbe_stop_and_truncate_event(struct perf_output_handle *handle)
> * When the write pointer reaches the address just before the limit pointer, it gets
> * wrapped around again to the base pointer. This is called a TRBE wrap event, which
> * generates a maintenance interrupt when operated in WRAP or FILL mode. This driver
> - * uses FILL mode, where the TRBE stops the trace collection at wrap event. The IRQ
> - * handler updates the AUX buffer and re-enables the TRBE with updated WRITE and
> - * LIMIT pointers.
> + * uses FILL mode for non-overwrite buffers, where the TRBE stops trace collection
> + * at a wrap event. The IRQ handler updates the AUX buffer and re-enables the TRBE
> + * with updated WRITE and LIMIT pointers. Snapshot buffers use Circular Buffer
> + * mode where possible, overwriting old trace without stopping or interrupting
> + * the CPU when the write pointer wraps.
> *
> * Wrap around with an IRQ
> * ------ < ------ < ------- < ----- < -----
> @@ -628,6 +631,7 @@ 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;
> + u64 mode = buf->circular ? TRBLIMITR_EL1_FM_CBUF : TRBLIMITR_EL1_FM_FILL;
>
> WARN_ON(!IS_ALIGNED(addr, (1UL << TRBLIMITR_EL1_LIMIT_SHIFT)));
> WARN_ON(!IS_ALIGNED(addr, PAGE_SIZE));
> @@ -638,15 +642,11 @@ static void set_trbe_limit_pointer_enabled(struct trbe_buf *buf)
> trblimitr &= ~TRBLIMITR_EL1_LIMIT_MASK;
>
> /*
> - * Fill trace buffer mode is used here while configuring the
> - * TRBE for trace capture. In this particular mode, the trace
> - * collection is stopped and a maintenance interrupt is raised
> - * when the current write pointer wraps. This pause in trace
> - * collection gives the software an opportunity to capture the
> - * trace data in the interrupt handler, before reconfiguring
> - * the TRBE.
> + * Circular mode keeps the most recent trace in a snapshot buffer
> + * without generating wrap interrupts. Use Fill mode otherwise so
> + * the IRQ handler can collect the trace before it is overwritten.
> */
> - trblimitr |= (TRBLIMITR_EL1_FM_FILL << TRBLIMITR_EL1_FM_SHIFT) &
> + trblimitr |= (mode << TRBLIMITR_EL1_FM_SHIFT) &
> TRBLIMITR_EL1_FM_MASK;
>
> /*
> @@ -757,6 +757,26 @@ static unsigned long trbe_get_trace_size(struct perf_output_handle *handle,
> return size;
> }
>
> +static unsigned long trbe_get_circular_size(struct perf_output_handle *handle,
> + struct trbe_buf *buf, u64 status)
> +{
> + u64 start = PERF_IDX2OFF(handle->head, buf);
> + u64 write = get_trbe_write_pointer() - buf->trbe_base;
> +
> + /*
> + * WRAP indicates at least one crossing of the limit. Any additional
> + * full laps cannot be determined, so account only for the trace
> + * known to have been collected. This also preserves the write offset
> + * when updating the head.
> + */
> + if (is_trbe_wrap(status))
> + write += (u64)buf->nr_pages << PAGE_SHIFT;
A wrap in snapshot mode only indicates a crossing of LIMIT, not when
data past the tail pointer has been overwritten (which is what snapshot
mode should actually consider as a wrap). For example for a buffer of
100 bytes, if you write 120 bytes from [head - 10 .. 10] it's marked as
"wrapped", then:
size = write + buffer_size - start
size = 10 + 100 - 90
size = 20
So it only reports a snapshot size of 20 bytes and the rest of the trace
is dropped.
The way to detect wraps for snapshot mode would be to set the trigger
counter to buffer_size, and when it reaches zero you know there has been
a wrap (overwriting data in the buffer) since the last snapshot, then
you can use size = buffer_size, otherwise with no wrap detected do the
calculation above.
I think this is technically a regression because the previous fill mode
handled both 1x and 2x wrap scenarios properly.
> + else if (WARN_ON_ONCE(write < start))
> + return 0;
> +
> + return write - start;
> +}
> +
> static void *arm_trbe_alloc_buffer(struct coresight_device *csdev,
> struct perf_event *event, void **pages,
> int nr_pages, bool snapshot)
> @@ -887,14 +907,19 @@ static unsigned long arm_trbe_update_buffer(struct coresight_device *csdev,
> wrap = true;
> }
>
> - size = trbe_get_trace_size(handle, buf, wrap);
> + if (buf->circular)
> + size = trbe_get_circular_size(handle, buf, status);
> + else
> + size = trbe_get_trace_size(handle, buf, wrap);
>
> done:
> local_irq_restore(flags);
>
> if (buf->snapshot)
> handle->head += size;
> - return size;
> +
> + /* At most one full buffer of trace is available */
> + return min(size, (unsigned long)buf->nr_pages << PAGE_SHIFT);
> }
>
>
> @@ -973,7 +998,8 @@ static int trbe_apply_work_around_before_enable(struct trbe_buf *buf)
> * - At trace collection:
> * - Pad the 256bytes skipped above again with IGNORE packets.
> */
> - if (trbe_has_erratum(buf->cpudata, TRBE_WORKAROUND_OVERWRITE_FILL_MODE)) {
> + if (!buf->circular &&
> + trbe_has_erratum(buf->cpudata, TRBE_WORKAROUND_OVERWRITE_FILL_MODE)) {
> if (WARN_ON(!IS_ALIGNED(buf->trbe_write, PAGE_SIZE)))
> return -EINVAL;
> buf->trbe_hw_base = buf->trbe_write;
> @@ -1056,6 +1082,13 @@ static int arm_trbe_enable(struct coresight_device *csdev, enum cs_mode mode,
> cpudata->mode = mode;
> buf->cpudata = cpudata;
>
> + /*
> + * A CPU affected by the write out-of-range erratum cannot use circular
> + * mode, as the TRBE continues tracing after wrapping and may write out
> + * of range. Fall back to FILL mode so the IRQ handler can fix this up.
> + */
> + buf->circular = buf->snapshot && !trbe_may_write_out_of_range(cpudata);
> +
> return __arm_trbe_enable(buf, handle);
> }
>
>
More information about the linux-arm-kernel
mailing list