On 07/08/2026 17:41, Athira Rajeev wrote:
> Add powerpc perf tool support for the HTM PMU AUXTRACE recording path.
> 
> Introduce htm_recording_init() and the associated auxtrace callbacks so
> perf record can create PERF_RECORD_AUXTRACE records for HTM data.
> 
> htm_info_fill() stores the PMU type, number of HTM evsels, and a
> (cpu, attr.config) pair for each evsel into PERF_RECORD_AUXTRACE_INFO
> priv[].  The decode side reads these back to map each AUX buffer
> (identified by event->auxtrace.cpu) to the correct (node, chip, core)
> target when writing htm.bin.* output files.
> 
> Update auxtrace_record__init() to detect HTM events and dispatch to the
> HTM-specific recording initializer.
> 
> Signed-off-by: Athira Rajeev <[email protected]>

A couple of very minor comments.

Nevertheless:

Reviewed-by: Adrian Hunter <[email protected]>

> ---
> Changes in V5:
> - htm_recording_options(): clamp aux_watermark with min_t(size_t, wm,
>   UINT_MAX) before assigning to pos->core.attr.aux_watermark (u32).
>   On a system with large auxtrace_mmap_pages the unclamped product can
>   exceed UINT_MAX and silently truncate on 32-bit platforms.
> - htm_info_fill(): iterate all CPUs in each evsel's CPU map and write
>   one (cpu, config) entry per CPU instead of only the first CPU.  An
>   evsel opened with -C 0,1,2 now contributes three priv[] entries, one
>   per CPU, each carrying the same attr.config.  The decode side
>   (powerpc_htm_process_auxtrace_event) uses event->auxtrace.cpu to look
>   up the matching entry, so every CPU's AUX buffer is correctly mapped
>   to its (node, chip, core, type) target and written to the right output
>   file.  An evsel with no CPU map records cpu=-1 as before.
> 
> Changes in V4:
> - Add explicit mutual-exclusion check in auxtrace_record__init(): if
>   both vpa_dtl_evsel and htm_evsel are set after scanning the evlist,
>   print an error, set *err = -EINVAL, and return NULL.  V3 silently
>   fell through to prefer VPA-DTL when both PMU types were present,
>   leaving HTM AUX buffers collected without a
>   PERF_RECORD_AUXTRACE_INFO record and the trace undecodable.
> - Drop the superfluous pos->name && null-guard from the HTM detection
>   condition in auxtrace_record__init(): the condition becomes
>   !strcmp(evsel__pmu_name(pos), "htm") without the leading check.
>   evsel__pmu_name() handles a NULL name internally so the guard is
>   redundant.
> - Apply the same simplification in htm_recording_options() and
>   htm_nr_events() in htm.c: remove the !pos->name || prefix from
>   the evsel__pmu_name() comparisons, consistent with the dispatcher
>   change above.
> - Set pos->core.attr.sample_period = 1 for each HTM evsel inside
>   htm_recording_options().  V3 set freq=0 and aux_watermark but did
>   not set sample_period, leaving the kernel to use whatever default
>   value was in the attr, which could cause the HTM event to fire at
>   an unintended rate.
> 
> Changes in V3:
> - Switch HTM event detection in auxtrace_record__init() from
> strstarts(pos->name, "htm") to !strcmp(evsel__pmu_name(pos), "htm"),
> matching the kernel-assigned PMU name and preventing false
> matches on user-named events.
> - Switch htm_recording_options(), htm_nr_events(), and the
> loop in htm_info_fill() from strstarts(pos->name, "htm")
> to evsel__pmu_name(), consistent with the above and with
> patches 4 and 6.
> - Add #include <linux/zalloc.h> to htm.c for zalloc().
> - Set auxtrace_info->type = PERF_AUXTRACE_POWERPC_HTM directly
> in htm_info_fill(), removing the placeholder comment. (V2
> deferred this to patch 5; it is cleaner to set it in the same
> patch that defines the enum.)
> - Set aux_watermark to half the AUX ring-buffer size
> (auxtrace_mmap_pages * page_size / 2) so there is always
> headroom for hardware to write while userspace drains. V2 set
> it to the full buffer size, risking data loss under back-pressure.
> - Add PERF_AUXTRACE_POWERPC_HTM to the enum auxtrace_type in
> util/auxtrace.h and add #include "powerpc-htm.h" plus a stub
> case PERF_AUXTRACE_POWERPC_HTM: (falls through to
> PERF_AUXTRACE_UNKNOWN) in util/auxtrace.c in this patch, so
> the enum is defined before it is used in htm_info_fill().
> (V2 did this in patch 5.)
> 
> Changes in V2:
> - htm_info_fill() now stores a (cpu, attr.config) pair for every HTM
>   evsel into PERF_RECORD_AUXTRACE_INFO priv[].  V1 stored only the PMU
>   type and a single config value; there was no per-CPU mapping.
> - The priv[] layout is formalised in util/powerpc-htm.h with enum
>   constants POWERPC_HTM_PMU_TYPE, POWERPC_HTM_NUM_EVENTS, and
>   POWERPC_HTM_EVENT_DATA and the helper macros HTM_AUXTRACE_PRIV_FIXED
>   and HTM_AUXTRACE_PRIV_SIZE(n).  V1 used bare numeric offsets.
> - PERF_SAMPLE_RAW is enabled in the recording options so that memory
>   configuration records emitted by the kernel driver are captured
>   alongside the AUX stream.  V1 added this in a later patch.
> - PERF_AUXTRACE_POWERPC_HTM type constant is set in htm_info_fill()
>   (wired up in patch 5 once the enum is defined).
> - Patch is now 2/6 instead of 2/9.
> 
>  tools/perf/arch/powerpc/util/Build      |   1 +
>  tools/perf/arch/powerpc/util/auxtrace.c |  22 +++
>  tools/perf/arch/powerpc/util/htm.c      | 185 ++++++++++++++++++++++++
>  tools/perf/util/auxtrace.c              |   2 +
>  tools/perf/util/auxtrace.h              |   1 +
>  tools/perf/util/powerpc-htm.h           |  43 ++++++
>  6 files changed, 254 insertions(+)
>  create mode 100644 tools/perf/arch/powerpc/util/htm.c
>  create mode 100644 tools/perf/util/powerpc-htm.h
> 
> diff --git a/tools/perf/arch/powerpc/util/Build 
> b/tools/perf/arch/powerpc/util/Build
> index 7819c8f5af2d..297152591046 100644
> --- a/tools/perf/arch/powerpc/util/Build
> +++ b/tools/perf/arch/powerpc/util/Build
> @@ -8,3 +8,4 @@ perf-util-$(CONFIG_LIBDW) += skip-callchain-idx.o
>  
>  perf-util-y += auxtrace.o
>  perf-util-y += vpa-dtl.o
> +perf-util-y += htm.o
> diff --git a/tools/perf/arch/powerpc/util/auxtrace.c 
> b/tools/perf/arch/powerpc/util/auxtrace.c
> index e04a0bd61755..9936d1910256 100644
> --- a/tools/perf/arch/powerpc/util/auxtrace.c
> +++ b/tools/perf/arch/powerpc/util/auxtrace.c
> @@ -12,13 +12,16 @@
>  #include "../../util/debug.h"
>  #include "../../util/auxtrace.h"
>  #include "../../util/powerpc-vpadtl.h"
> +#include "../../util/powerpc-htm.h"
>  #include "../../util/record.h"
> +#include <string.h>

There is "#include <errno.h>" further up.  Maybe nicer to put them
together as standard library includes

>  
>  struct auxtrace_record *auxtrace_record__init(struct evlist *evlist,
>                                               int *err)
>  {
>       struct evsel *pos;
>       struct evsel *vpa_dtl_evsel = NULL;
> +     struct evsel *htm_evsel = NULL;
>  
>       /*
>        * Set err value to zero here. Any fail later
> @@ -32,11 +35,30 @@ struct auxtrace_record *auxtrace_record__init(struct 
> evlist *evlist,
>                       /* Remember the first matching VPA DTL event */
>                       if (!vpa_dtl_evsel)
>                               vpa_dtl_evsel = pos;
> +             } else if (!strcmp(evsel__pmu_name(pos), "htm")) {
> +                     pos->needs_auxtrace_mmap = true;
> +                     /* Remember the first matching HTM event */
> +                     if (!htm_evsel)
> +                             htm_evsel = pos;
>               }
>       }
>  
> +     /*
> +      * Only one auxtrace PMU can be initialised per session.  Reject
> +      * concurrent VPA DTL and HTM events: HTM AUX buffers would be
> +      * collected without a PERF_RECORD_AUXTRACE_INFO record, making
> +      * the trace undecodable.
> +      */
> +     if (vpa_dtl_evsel && htm_evsel) {
> +             pr_err("Cannot record VPA DTL and HTM auxtrace events 
> simultaneously\n");
> +             *err = -EINVAL;
> +             return NULL;
> +     }
> +
>       if (vpa_dtl_evsel)
>               return vpa_dtl_recording_init(vpa_dtl_evsel, err);
> +     else if (htm_evsel)
> +             return htm_recording_init(htm_evsel, err);
>  
>       return NULL;
>  }
> diff --git a/tools/perf/arch/powerpc/util/htm.c 
> b/tools/perf/arch/powerpc/util/htm.c
> new file mode 100644
> index 000000000000..d2fdef488032
> --- /dev/null
> +++ b/tools/perf/arch/powerpc/util/htm.c
> @@ -0,0 +1,185 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * HTM AUX tracing support
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/string.h>
> +#include <linux/zalloc.h>
> +#include <stdlib.h>
> +#include <limits.h>
> +#include "../../util/evsel.h"
> +#include "../../util/evlist.h"
> +#include "../../util/session.h"
> +#include "../../util/debug.h"
> +#include "../../util/auxtrace.h"
> +#include "../../util/powerpc-htm.h"
> +#include "../../util/record.h"
> +#include <internal/lib.h> /* page_size */
> +#include <errno.h>
> +
> +#define KiB(x) ((x) * 1024)
> +
> +struct htm_recording {
> +     struct auxtrace_record  itr;
> +     struct evsel            *evsel;
> +};
> +
> +static int
> +htm_recording_options(struct auxtrace_record *itr __maybe_unused,
> +                   struct evlist *evlist,
> +                   struct record_opts *opts)
> +{
> +     struct evsel *pos;
> +
> +     opts->full_auxtrace = true;
> +
> +     if (!opts->auxtrace_mmap_pages) {
> +             opts->auxtrace_mmap_pages = KiB(128) / page_size;
> +             if (opts->mmap_pages == UINT_MAX)
> +                     opts->mmap_pages = KiB(256) / page_size;
> +     }
> +
> +     evlist__for_each_entry(evlist, pos) {
> +             size_t wm;
> +
> +             if (strcmp(evsel__pmu_name(pos), "htm"))
> +                     continue;
> +             wm = opts->auxtrace_mmap_pages * (size_t)page_size / 2;
> +             pos->core.attr.aux_watermark = min_t(size_t, wm, UINT_MAX);
> +             pos->core.attr.sample_type |= PERF_SAMPLE_RAW;
> +             pos->core.attr.freq = 0;
> +             pos->core.attr.sample_period = 1;
> +             pos->needs_auxtrace_mmap = true;
> +     }
> +
> +     return 0;
> +}
> +
> +/* Count htm evsels in the evlist */
> +static int htm_nr_events(struct evlist *evlist)
> +{
> +     struct evsel *pos;
> +     int n = 0;
> +
> +     evlist__for_each_entry(evlist, pos) {
> +             if (!strcmp(evsel__pmu_name(pos), "htm"))
> +                     n++;
> +     }
> +     return n;
> +}
> +
> +static size_t htm_info_priv_size(struct auxtrace_record *itr __maybe_unused,
> +                              struct evlist *evlist)
> +{
> +     return HTM_AUXTRACE_PRIV_SIZE(htm_nr_events(evlist));
> +}
> +
> +/*
> + * Fill the PERF_RECORD_AUXTRACE_INFO private data with:
> + *   priv[POWERPC_HTM_PMU_TYPE]   = pmu->type of the first htm evsel
> + *   priv[POWERPC_HTM_NUM_EVENTS] = total number of HTM CPU entries
> + *   priv[POWERPC_HTM_EVENT_DATA + n*2]     = CPU number for nth entry
> + *   priv[POWERPC_HTM_EVENT_DATA + n*2 + 1] = attr.config for nth entry
> + *
> + * One entry is written per CPU in each evsel's cpu map.  An evsel opened
> + * with -C 0,1,2 contributes three entries (one per CPU), each carrying
> + * the same attr.config.  The decode side uses event->auxtrace.cpu to look
> + * up the matching config and derive (node, chip, core) for the output
> + * file name.
> + */
> +static int
> +htm_info_fill(struct auxtrace_record *itr,
> +           struct perf_session *session,
> +           struct perf_record_auxtrace_info *auxtrace_info,
> +           size_t priv_size)
> +{
> +     struct htm_recording *htm_r = container_of(itr, struct htm_recording, 
> itr);
> +     struct evlist *evlist = session->evlist;
> +     struct evsel *pos;
> +     int n = 0;
> +     int expected_n = htm_nr_events(evlist);
> +
> +     if (priv_size != HTM_AUXTRACE_PRIV_SIZE(expected_n))
> +             return -EINVAL;
> +
> +     auxtrace_info->type = PERF_AUXTRACE_POWERPC_HTM;
> +     auxtrace_info->priv[POWERPC_HTM_PMU_TYPE] = 
> htm_r->evsel->core.attr.type;
> +     auxtrace_info->priv[POWERPC_HTM_NUM_EVENTS] = expected_n;
> +
> +     evlist__for_each_entry(evlist, pos) {
> +             struct perf_cpu_map *cpus;
> +             int i, nr;
> +
> +             if (strcmp(evsel__pmu_name(pos), "htm"))
> +                     continue;
> +
> +             /*
> +              * Emit one (cpu, config) entry for every CPU in this evsel's
> +              * map.  perf record -C 0,1,2 creates one evsel with a
> +              * three-entry cpu map; each CPU gets its own AUX buffer and
> +              * must be individually mapped so the decoder can match
> +              * event->auxtrace.cpu to the correct (node, chip, core).
> +              */
> +             cpus = evsel__cpus(pos);
> +             nr = cpus ? perf_cpu_map__nr(cpus) : 0;
> +
> +             if (nr > 0) {
> +                     for (i = 0; i < nr; i++) {
> +                             int cpu = perf_cpu_map__cpu(cpus, i).cpu;
> +
> +                             auxtrace_info->priv[POWERPC_HTM_EVENT_DATA + n 
> * 2]     = cpu;
> +                             auxtrace_info->priv[POWERPC_HTM_EVENT_DATA + n 
> * 2 + 1] =
> +                                                             
> pos->core.attr.config;
> +                             n++;
> +                     }
> +             } else {
> +                     /* cpu-agnostic evsel: record cpu = -1 */
> +                     auxtrace_info->priv[POWERPC_HTM_EVENT_DATA + n * 2]     
> = (u64)-1;
> +                     auxtrace_info->priv[POWERPC_HTM_EVENT_DATA + n * 2 + 1] 
> =
> +                                                     pos->core.attr.config;
> +                     n++;
> +             }
> +     }
> +
> +     return 0;
> +}
> +
> +static u64 htm_reference(struct auxtrace_record *itr __maybe_unused)
> +{
> +     return 0;
> +}
> +
> +static void htm_free(struct auxtrace_record *itr)
> +{
> +     struct htm_recording *htm_r = container_of(itr, struct htm_recording, 
> itr);
> +
> +     free(htm_r);
> +}
> +
> +struct auxtrace_record *htm_recording_init(struct evsel *pos, int *err)
> +{
> +     struct htm_recording *htm_r;
> +
> +     /*
> +      * To obtain the auxtrace buffer file descriptor, the auxtrace event
> +      * must come first.
> +      */
> +     evlist__to_front(pos->evlist, pos);
> +
> +     htm_r = zalloc(sizeof(*htm_r));
> +     if (!htm_r) {
> +             pr_debug("htm_recording allocation failed (-ENOMEM)\n");
> +             *err = -ENOMEM;
> +             return NULL;
> +     }
> +
> +     htm_r->evsel = pos;
> +     htm_r->itr.recording_options = htm_recording_options;
> +     htm_r->itr.info_priv_size    = htm_info_priv_size;
> +     htm_r->itr.info_fill         = htm_info_fill;
> +     htm_r->itr.free              = htm_free;
> +     htm_r->itr.reference         = htm_reference;
> +     return &htm_r->itr;
> +}
> diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c
> index aa749e1c3036..bf08f41d623f 100644
> --- a/tools/perf/util/auxtrace.c
> +++ b/tools/perf/util/auxtrace.c
> @@ -56,6 +56,7 @@
>  #include "s390-cpumsf.h"
>  #include "util/mmap.h"
>  #include "powerpc-vpadtl.h"
> +#include "powerpc-htm.h"
>  
>  #include <linux/ctype.h>
>  #include "symbol/kallsyms.h"
> @@ -1432,6 +1433,7 @@ int perf_event__process_auxtrace_info(const struct 
> perf_tool *tool __maybe_unuse
>       case PERF_AUXTRACE_VPA_DTL:
>               err = powerpc_vpadtl_process_auxtrace_info(event, session);
>               break;
> +     case PERF_AUXTRACE_POWERPC_HTM:
>       case PERF_AUXTRACE_UNKNOWN:
>       default:
>               return -EINVAL;
> diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h
> index 6947f3f284c0..68b17802a419 100644
> --- a/tools/perf/util/auxtrace.h
> +++ b/tools/perf/util/auxtrace.h
> @@ -46,6 +46,7 @@ enum auxtrace_type {
>       PERF_AUXTRACE_S390_CPUMSF,
>       PERF_AUXTRACE_HISI_PTT,
>       PERF_AUXTRACE_VPA_DTL,
> +     PERF_AUXTRACE_POWERPC_HTM,
>  };
>  
>  enum itrace_period_type {
> diff --git a/tools/perf/util/powerpc-htm.h b/tools/perf/util/powerpc-htm.h
> new file mode 100644
> index 000000000000..18e39417d556
> --- /dev/null
> +++ b/tools/perf/util/powerpc-htm.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __POWERPC_HTM_H
> +#define __POWERPC_HTM_H
> +
> +#include <linux/types.h>
> +
> +/*
> + * Layout of the private data in PERF_RECORD_AUXTRACE_INFO for HTM.
> + *
> + * priv[POWERPC_HTM_PMU_TYPE]   = htm PMU type ID (pmu->type from kernel)
> + * priv[POWERPC_HTM_NUM_EVENTS] = number of htm evsels recorded (N)
> + *
> + * Followed by N pairs (2 u64 each):
> + *   priv[POWERPC_HTM_EVENT_DATA + n*2 + 0] = CPU number for nth htm evsel
> + *   priv[POWERPC_HTM_EVENT_DATA + n*2 + 1] = attr.config for nth htm evsel
> + *
> + * Total priv entries: POWERPC_HTM_EVENT_DATA + N * 2
> + */
> +enum {
> +     POWERPC_HTM_PMU_TYPE  = 0,
> +     POWERPC_HTM_NUM_EVENTS,
> +     POWERPC_HTM_EVENT_DATA, /* variable-length: 2 u64 per event */
> +};
> +
> +/* Fixed header size (everything before the per-event data) */
> +#define HTM_AUXTRACE_PRIV_FIXED  (POWERPC_HTM_EVENT_DATA * sizeof(u64))
> +
> +/* Total priv size for N htm evsels */
> +#define HTM_AUXTRACE_PRIV_SIZE(n) \
> +     (HTM_AUXTRACE_PRIV_FIXED + (n) * 2 * sizeof(u64))
> +
> +struct evsel;
> +struct evlist;
> +union perf_event;
> +struct perf_session;
> +struct auxtrace_record;
> +
> +struct auxtrace_record *htm_recording_init(struct evsel *pos, int *err);
> +
> +int powerpc_htm_process_auxtrace_info(union perf_event *event,
> +                                   struct perf_session *session);

Ideally, should only add a declaration for powerpc_htm_process_auxtrace_info()
when it actually exists

> +
> +#endif /* __POWERPC_HTM_H */


Reply via email to