perf cs-etm: Synthesize missing HW_ID mappings for raw trace

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@arm.com>
Signed-off-by: James Clark <james.clark@linaro.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Leo Yan 2026-10-02 12:15:13 +01:00 committed by Arnaldo Carvalho de Melo
parent 18eaa04d94
commit e4c26138ec

View File

@ -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;