[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