[PATCH v2 3/4] perf cs-etm: Synthesize missing HW_ID mappings for raw trace

From: James Clark

Date: Fri Oct 02 2026 - 07:06:25 EST


From: Leo Yan <leo.yan@xxxxxxx>

The CoreSight driver emits PERF_RECORD_AUX_OUTPUT_HW_ID via
perf_report_aux_output_id(). If the perf ring buffer is full,
perf_report_aux_output_id() can fail to emit the record and return
silently.

When at least one HW_ID record is present in the file, perf skips the
legacy metadata-only trace ID mapping path. Therefore any raw AUX queue
whose HW_ID record was dropped can be left with an empty traceid_list,
and cs_etm__create_queue_decoders() will not create a decoder for that
queue.

Fix it by ignoring all HW_IDs for unformatted queues and always create
one decoder per queue unconditionally. Trace ID is unused so we can hard
code it to 1 (0 is a special value used in
'pending_timestamp_chan_id' to indicate not pending). We also need put
the same trace ID in the CPU metadata because the relationship between
CPU metadata and traceid_list is bidirectional. Moving
cs_etm__metadata_set_trace_id() into the mapping function solves this
and simplifies existing code. cs_etm__insert_trace_id_node() now does
two things, so rename it to a more generic cs_etm__map_trace_id().

Signed-off-by: Leo Yan <leo.yan@xxxxxxx>
Signed-off-by: James Clark <james.clark@xxxxxxxxxx>
---
tools/perf/util/cs-etm.c | 85 +++++++++++++++++++++++++++++++++---------------
1 file changed, 58 insertions(+), 27 deletions(-)

diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c
index 78ae8443ac96..c0152549e3e2 100644
--- a/tools/perf/util/cs-etm.c
+++ b/tools/perf/util/cs-etm.c
@@ -243,8 +243,12 @@ enum cs_etm_pid_fmt cs_etm__get_pid_fmt(struct cs_etm_queue *etmq)
return etmq->etm->pid_fmt;
}

-static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq,
- u8 trace_chan_id, u64 *cpu_metadata)
+/*
+ * Add a traceid_list entry that maps a trace ID to a CPU metadata for this etmq
+ * and update the trace ID in the cpu_metadata.
+ */
+static int cs_etm__map_trace_id(struct cs_etm_queue *etmq, u8 trace_chan_id,
+ u64 *cpu_metadata)
{
/* Get an RB node for this CPU */
struct int_node *inode = intlist__findnew(etmq->traceid_list, trace_chan_id);
@@ -289,8 +293,7 @@ static int cs_etm__insert_trace_id_node(struct cs_etm_queue *etmq,

/* Not one we've seen before, associate the traceID with the metadata pointer */
inode->priv = cpu_metadata;
-
- return 0;
+ return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_metadata);
}

static struct cs_etm_queue *cs_etm__get_queue(struct cs_etm_auxtrace *etm, int cpu)
@@ -314,12 +317,11 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
return -EINVAL;

/*
- * If the queue is unformatted then only save one mapping in the
- * queue associated with that CPU so only one decoder is made.
+ * Ignore HW_IDs for unformatted, a decoder will be made
+ * unconditionally later.
*/
if (etmq->format == UNFORMATTED)
- return cs_etm__insert_trace_id_node(etmq, trace_chan_id,
- cpu_metadata);
+ return 0;

/*
* Otherwise, version 0 trace IDs are global so save them into every
@@ -332,8 +334,7 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
if (!etmq || etmq->format == UNFORMATTED)
continue;

- ret = cs_etm__insert_trace_id_node(etmq, trace_chan_id,
- cpu_metadata);
+ ret = cs_etm__map_trace_id(etmq, trace_chan_id, cpu_metadata);
if (ret)
return ret;
}
@@ -344,7 +345,6 @@ static int cs_etm__map_trace_id_v0(struct cs_etm_auxtrace *etm, u8 trace_chan_id
static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu,
u64 hw_id)
{
- int err;
u64 *cpu_data;
u8 trace_chan_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id);

@@ -352,22 +352,13 @@ static int cs_etm__process_trace_id_v0(struct cs_etm_auxtrace *etm, int cpu,
if (cpu_data == NULL)
return -EINVAL;

- err = cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data);
- if (err)
- return err;
-
- /*
- * if we are picking up the association from the packet, need to plug
- * the correct trace ID into the metadata for setting up decoders later.
- */
- return cs_etm__metadata_set_trace_id(trace_chan_id, cpu_data);
+ return cs_etm__map_trace_id_v0(etm, trace_chan_id, cpu_data);
}

static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
u64 hw_id)
{
struct cs_etm_queue *etmq = cs_etm__get_queue(etm, cpu);
- int ret;
u64 *cpu_data;
u32 sink_id = FIELD_GET(CS_AUX_HW_ID_SINK_ID_MASK, hw_id);
u8 trace_id = FIELD_GET(CS_AUX_HW_ID_TRACE_ID_MASK, hw_id);
@@ -375,6 +366,13 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
if (!etmq)
return -EINVAL;

+ /*
+ * Ignore HW_IDs for unformatted, a decoder will be made
+ * unconditionally later.
+ */
+ if (etmq->format == UNFORMATTED)
+ return 0;
+
/*
* Check sink id hasn't changed in per-cpu mode. In per-thread mode,
* let it pass for now until an actual overlapping trace ID is hit. In
@@ -419,13 +417,41 @@ static int cs_etm__process_trace_id_v0_1(struct cs_etm_auxtrace *etm, int cpu,
if (!cpu_data)
return -EINVAL;

- ret = cs_etm__insert_trace_id_node(etmq, trace_id, cpu_data);
- if (ret)
- return ret;
+ return cs_etm__map_trace_id(etmq, trace_id, cpu_data);
+}

- ret = cs_etm__metadata_set_trace_id(trace_id, cpu_data);
- if (ret)
- return ret;
+static int cs_etm__map_unformatted_trace_ids(struct cs_etm_auxtrace *etm)
+{
+ struct auxtrace_queues *queues = &etm->queues;
+
+ for (unsigned int i = 0; i < queues->nr_queues; i++) {
+ struct auxtrace_queue *queue = &queues->queue_array[i];
+ struct cs_etm_queue *etmq = queue->priv;
+ u64 *cpu_data;
+ int ret;
+
+ if (list_empty(&queue->head) || !etmq || etmq->format != UNFORMATTED)
+ continue;
+
+ if (!intlist__empty(etmq->traceid_list)) {
+ pr_err("CS_ETM: Unformatted queue should't have trace ID\n");
+ return -EINVAL;
+ }
+
+ if (etm->per_thread_decoding)
+ cpu_data = etm->metadata[0];
+ else
+ cpu_data = get_cpu_data(etm, queue->cpu);
+
+ if (!cpu_data) {
+ pr_err("CS_ETM: Unformatted queue missing CPU metadata\n");
+ return -EINVAL;
+ }
+
+ ret = cs_etm__map_trace_id(etmq, 1, cpu_data);
+ if (ret)
+ return ret;
+ }

return 0;
}
@@ -3757,6 +3783,11 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event,
goto err_free_queues;
}

+ /* Create IDs for TRBE sinks so that a decoder is made for each one. */
+ err = cs_etm__map_unformatted_trace_ids(etm);
+ if (err)
+ goto err_free_queues;
+
err = cs_etm__create_decoders(etm);
if (err)
goto err_free_queues;

--
2.43.0