* [PATCH V3 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
The H_HTM hypervisor call (hcall) provides an interface to the Hardware
Trace Macro (HTM) function on POWER systems. HTM captures hardware-level
trace data for a specific node/chip/core target within a logical partition.
Add a new "htm" Performance Monitoring Unit (PMU) that exposes HTM control
via the standard perf interface. The event configuration is a 28-bit value
packed into the perf config field:
bits 0-3: htm_type (HTM_CORE=2, HTM_NEST=1)
bits 4-11: nodeindex
bits 12-19: nodalchipindex
bits 20-27: coreindexonchip
PMU lifecycle:
add/del: Issue H_HTM_OP_CONFIGURE / H_HTM_OP_DECONFIGURE to reserve
and release the hardware trace resource.
start/stop: Issue H_HTM_OP_START / H_HTM_OP_STOP to control tracing.
HTM traces at node/chip/core scope continuously for the
duration of the event. Context-switch-triggered start/stop
callbacks (PERF_EF_RELOAD / PERF_EF_UPDATE) are ignored to
keep tracing uninterrupted. Explicit ioctl(ENABLE/DISABLE)
and event deletion do control the hardware.
State tracking uses event->pmu_private (HTM_TRACING_ACTIVE/INACTIVE) as
the source of information, with event->hw.state kept in sync as a hint
for the perf core. This avoids conflicts with infrastructure writes to
event->hw.state.
H_BUSY, H_LONG_BUSY_* and other erros handling:
All PMU callbacks (add, del, start, stop) are invoked in an atomic
context with interrupts disabled and hardware context locks held;
sleeping is not possible anywhere in the driver.
H_BUSY (transient): retried in a spin loop up to MAX_RETRIES times.
H_LONG_BUSY_* (hypervisor requests a long delay before retry) and
other errors: not retried. The correct response differs by callsite:
- pmu->add (H_HTM_OP_CONFIGURE): returns -ENODEV on any non-success
result from the configure hcall. Returning -EAGAIN from pmu->add()
would cause the perf core to re-queue the event and call pmu->add()
again on every context switch, flooding the hypervisor with
H_HTM_OP_CONFIGURE hcalls exactly when it has requested a long
backoff delay. -ENODEV is a hard failure that the perf core does
not reschedule.
- pmu->stop (H_HTM_OP_STOP): exits immediately on any failure,
leaving tracing_active as HTM_TRACING_ACTIVE. The perf core always
calls pmu->del() after pmu->stop(), so htm_event_del() is the
guaranteed retry point for the stop hcall.
- pmu->del (H_HTM_OP_STOP retry and H_HTM_OP_DECONFIGURE): retries
the stop hcall first. H_HTM_OP_DECONFIGURE requires the trace to be
stopped first; if the stop retry fails, the deconfigure will
fail too. Both failures are surfaced via pr_err with target
identifiers (node/chip/core/type).
A workqueue-based deferred cleanup was considered but rejected as
over-engineering for a firmware-maintenance-only edge case that is
consistent with how all other in-tree POWER hcall-backed PMU drivers
Note: this patch does not prevent two concurrent perf_event_open() calls
for the same HTM hardware target (node/chip/core) from being opened
simultaneously on different CPUs. PERF_PMU_CAP_EXCLUSIVE only enforces
exclusivity within the same per-CPU context and does not cover this case.
A global reserved-targets list to reject duplicate reservations is added
in the next patch.
After this patch the PMU is visible under sysfs:
# ls /sys/bus/event_source/devices/ |grep htm
htm
# ls /sys/bus/event_source/devices/htm/
events format perf_event_mux_interval_ms power subsystem type uevent
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Expanded the H_BUSY, H_LONG_BUSY_* and other errors handling section to
give per-callsite rationale for each hcall (configure, stop, del).
V2 described the retry policy but not why each site responds differently.
- Added explicit note that this patch does not yet prevent duplicate concurrent
perf_event_open() calls for the same HTM target; that is handled in patch 2.
V2 omitted this cross-reference.
- Diffstat grows from 379 to 422 lines added (the extra lines come from the
expanded commit message body; no functional change to the driver code
itself).
- No functional change to the driver code in this patch.
Changes in V2:
- Moved the HTM PMU driver from arch/powerpc/htm/ to arch/powerpc/perf/
and renamed the source file from htm.c to htm-perf.c to follow the
naming convention of other PMU drivers in that directory.
- Replaced direct H_HTM_OP_CONFIGURE/DECONFIGURE calls in htm_event_add()
and htm_event_del() with separate htm_event_init() and destroy paths.
State tracking is now via event->pmu_private (HTM_TRACING_ACTIVE /
HTM_TRACING_INACTIVE) as the authoritative source, with event->hw.state
kept in sync as a hint for the perf core, avoiding conflicts with
infrastructure writes to event->hw.state.
- Context-switch callbacks (PERF_EF_RELOAD / PERF_EF_UPDATE) are now
explicitly ignored in htm_event_start()/stop() to keep tracing
uninterrupted across task switches; previously these paths were absent.
- H_BUSY retry loop is capped at MAX_RETRIES; H_LONG_BUSY_* is not
retried in atomic context to avoid deadlocks (was unconditionally
retried in V1).
arch/powerpc/perf/Makefile | 2 +-
arch/powerpc/perf/htm-perf.c | 422 +++++++++++++++++++++++++++++++++++
2 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 arch/powerpc/perf/htm-perf.c
diff --git a/arch/powerpc/perf/Makefile b/arch/powerpc/perf/Makefile
index 78dd7e25219e..26ef30c0693c 100644
--- a/arch/powerpc/perf/Makefile
+++ b/arch/powerpc/perf/Makefile
@@ -14,7 +14,7 @@ obj-$(CONFIG_PPC_POWERNV) += imc-pmu.o
obj-$(CONFIG_FSL_EMB_PERF_EVENT) += core-fsl-emb.o
obj-$(CONFIG_FSL_EMB_PERF_EVENT_E500) += e500-pmu.o e6500-pmu.o
-obj-$(CONFIG_HV_PERF_CTRS) += hv-24x7.o hv-gpci.o hv-common.o vpa-dtl.o
+obj-$(CONFIG_HV_PERF_CTRS) += hv-24x7.o hv-gpci.o hv-common.o vpa-dtl.o htm-perf.o
obj-$(CONFIG_VPA_PMU) += vpa-pmu.o
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
new file mode 100644
index 000000000000..4e4c924ecfd0
--- /dev/null
+++ b/arch/powerpc/perf/htm-perf.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Perf interface to expose HTM Trace data.
+ *
+ * Copyright (C) 2026 Athira Rajeev, IBM Corporation
+ */
+
+#define pr_fmt(fmt) "htm: " fmt
+
+#include <asm/dtl.h>
+#include <linux/perf_event.h>
+#include <asm/plpar_wrappers.h>
+#include <asm/firmware.h>
+
+#define EVENT(_name, _code) enum{_name = _code}
+#define MAX_RETRIES 100
+
+EVENT(HTM_CORE, 0x2);
+EVENT(HTM_NEST, 0x1);
+
+GENERIC_EVENT_ATTR(htm_core, HTM_CORE);
+GENERIC_EVENT_ATTR(htm_nest, HTM_NEST);
+
+PMU_FORMAT_ATTR(event, "config:0-27");
+PMU_FORMAT_ATTR(htm_type, "config:0-3");
+PMU_FORMAT_ATTR(nodeindex, "config:4-11");
+PMU_FORMAT_ATTR(nodalchipindex, "config:12-19");
+PMU_FORMAT_ATTR(coreindexonchip, "config:20-27");
+
+static struct attribute *events_attr[] = {
+ GENERIC_EVENT_PTR(HTM_NEST),
+ GENERIC_EVENT_PTR(HTM_CORE),
+ NULL
+};
+
+static struct attribute_group event_group = {
+ .name = "events",
+ .attrs = events_attr,
+};
+
+static struct attribute *format_attrs[] = {
+ &format_attr_event.attr,
+ &format_attr_htm_type.attr,
+ &format_attr_nodeindex.attr,
+ &format_attr_nodalchipindex.attr,
+ &format_attr_coreindexonchip.attr,
+ NULL,
+};
+
+static const struct attribute_group format_group = {
+ .name = "format",
+ .attrs = format_attrs,
+};
+
+static const struct attribute_group *attr_groups[] = {
+ &format_group,
+ &event_group,
+ NULL,
+};
+
+static u64 htmflags = H_HTM_FLAGS_NOWRAP;
+
+struct htm_config {
+ u32 htmtype;
+ u32 nodeindex;
+ u32 nodalchipindex;
+ u32 coreindexonchip;
+};
+
+/*
+ * Per-event private state. Allocated in htm_event_init(), freed via the
+ * event->destroy callback (reset_htm_active()).
+ *
+ * cfg stores the HTM target identity parsed from event->attr.config.
+ * tracing_active tracks whether H_HTM_OP_START has been successfully issued
+ * for this event. It is the source of information used by
+ * htm_event_start() and htm_event_stop() to make hcall decisions.
+ * event->hw.state is kept in sync for the perf core only.
+ */
+struct htm_target_id {
+ struct htm_config cfg;
+ int tracing_active; /* HTM_TRACING_ACTIVE / HTM_TRACING_INACTIVE */
+};
+
+/* Helper to parse the 28-bit event config into distinct fields */
+static inline void parse_htm_config(u64 config, struct htm_config *cfg)
+{
+ cfg->htmtype = config & 0xf;
+ cfg->nodeindex = (config >> 4) & 0xff;
+ cfg->nodalchipindex = (config >> 12) & 0xff;
+ cfg->coreindexonchip = (config >> 20) & 0xff;
+}
+
+/*
+ * Check the return code for H_HTM hcall.
+ * Return 1 if either H_PARTIAL or H_SUCCESS is returned.
+ * Return 0 if H_NOT_AVAILABLE.
+ * Return exact negative error codes for expected issues.
+ */
+static ssize_t htm_return_check(int rc)
+{
+ switch (rc) {
+ case H_SUCCESS:
+ case H_PARTIAL:
+ return 1;
+ case H_NOT_AVAILABLE:
+ return 0;
+ case H_BUSY:
+ /* Transient busy: retry loop will spin up to MAX_RETRIES */
+ return -EBUSY;
+ case H_LONG_BUSY_ORDER_1_MSEC:
+ case H_LONG_BUSY_ORDER_10_MSEC:
+ case H_LONG_BUSY_ORDER_100_MSEC:
+ case H_LONG_BUSY_ORDER_1_SEC:
+ case H_LONG_BUSY_ORDER_10_SEC:
+ case H_LONG_BUSY_ORDER_100_SEC:
+ /*
+ * Hypervisor requests a long delay before retry. All PMU
+ * callbacks (add, del, start, stop) are invoked in an atomic
+ * context with interrupts disabled and hardware context locks
+ * held. sleeping is not possible anywhere in the driver.
+ * Return -EAGAIN so every caller can distinguish this from
+ * transient H_BUSY and treat it as a hard failure without
+ * spinning or sleeping. See the per-callsite comments for how
+ * each caller handles it.
+ */
+ return -EAGAIN;
+ case H_PARAMETER:
+ case H_P2:
+ case H_P3:
+ case H_P4:
+ case H_P5:
+ case H_P6:
+ return -EINVAL;
+ case H_STATE:
+ return -EIO;
+ case H_AUTHORITY:
+ return -EPERM;
+ default:
+ /* Prevent silent fallthrough mapping of unhandled errors to 1 */
+ return -EIO;
+ }
+}
+
+/*
+ * HTM_TRACING_ACTIVE/INACTIVE: values for htm_target_id.tracing_active.
+ * Tracks whether H_HTM_OP_START has been successfully issued.
+ * HTM traces at node/chip/core scope, not per-task. Once started,
+ * context-switch-triggered stop/start (PERF_EF_UPDATE / PERF_EF_RELOAD)
+ * must not stop/restart the hardware. Only explicit API calls
+ * (ioctl DISABLE/ENABLE) and event_del should control the hcall.
+ */
+#define HTM_TRACING_ACTIVE 1
+#define HTM_TRACING_INACTIVE 0
+
+static void reset_htm_active(struct perf_event *event)
+{
+ kfree(event->pmu_private);
+ event->pmu_private = NULL;
+}
+
+static int htm_event_init(struct perf_event *event)
+{
+ u64 config = event->attr.config;
+ struct htm_config cfg;
+
+ if (event->attr.inherit)
+ return -EOPNOTSUPP;
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ if (!perfmon_capable())
+ return -EACCES;
+
+ if (!is_sampling_event(event))
+ return -EOPNOTSUPP;
+
+ if (has_branch_stack(event))
+ return -EOPNOTSUPP;
+
+ parse_htm_config(config, &cfg);
+ switch (cfg.htmtype) {
+ case HTM_CORE:
+ case HTM_NEST:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Allocate per-event private state; freed via event->destroy */
+ event->pmu_private = kzalloc(sizeof(struct htm_target_id), GFP_KERNEL);
+ if (!event->pmu_private)
+ return -ENOMEM;
+
+ ((struct htm_target_id *)event->pmu_private)->cfg = cfg;
+ event->destroy = reset_htm_active;
+ return 0;
+}
+
+static void htm_event_start(struct perf_event *event, int flags)
+{
+ int rc, ret, retries = 0;
+ struct htm_config cfg;
+ struct htm_target_id *target = event->pmu_private;
+
+ /*
+ * Ignore context-switch re-enables. The perf core passes
+ * PERF_EF_RELOAD on context-switch-in. HTM tracing is
+ * continuous at hardware level, no hcall needed.
+ */
+ if (flags & PERF_EF_RELOAD)
+ return;
+
+ /* Already tracing, don't issue a second start hcall */
+ if (target->tracing_active == HTM_TRACING_ACTIVE)
+ return;
+
+ cfg = target->cfg;
+
+ /* Only retry on transient H_BUSY; H_LONG_BUSY_* (-EAGAIN) exits immediately */
+ do {
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype,
+ H_HTM_OP_START, 0, 0, 0);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ if (ret > 0) {
+ target->tracing_active = HTM_TRACING_ACTIVE;
+ event->hw.state &= ~PERF_HES_STOPPED;
+ }
+}
+
+static void htm_event_stop(struct perf_event *event, int flags)
+{
+ int rc, ret, retries = 0;
+ struct htm_config cfg;
+ struct htm_target_id *target = event->pmu_private;
+
+ /*
+ * Ignore context-switch-out stops. The perf core passes
+ * PERF_EF_UPDATE on context-switch-out. Do not stop the hcall.
+ */
+ if (flags & PERF_EF_UPDATE)
+ return;
+
+ /* Not tracing, nothing to stop */
+ if (target->tracing_active == HTM_TRACING_INACTIVE)
+ return;
+
+ cfg = target->cfg;
+
+ /* Only retry on transient H_BUSY; H_LONG_BUSY_* (-EAGAIN) exits immediately */
+ do {
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype,
+ H_HTM_OP_STOP, 0, 0, 0);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ /*
+ * Only mark stopped if the hcall succeeded. If the stop failed
+ * (e.g. -EAGAIN on long-busy), leave tracing_active as ACTIVE so
+ * that htm_event_del will retry the stop hcall rather than
+ * skipping it and leaving the hypervisor permanently configured.
+ */
+ if (ret > 0) {
+ target->tracing_active = HTM_TRACING_INACTIVE;
+ event->hw.state |= PERF_HES_STOPPED;
+ }
+}
+
+static int htm_event_add(struct perf_event *event, int flags)
+{
+ int rc, ret, retries = 0;
+ unsigned long param1 = -1, param2 = -1;
+ struct htm_target_id *target = event->pmu_private;
+ struct htm_config cfg = target->cfg;
+
+ /*
+ * pmu->add() is invoked in an atomic context with interrupts disabled
+ * and hardware context locks held. sleeping is impossible. Only retry
+ * on transient H_BUSY. On H_LONG_BUSY_* (-EAGAIN) and other errors, the
+ * loop exits and we return -ENODEV, which is a hard failure: the perf
+ * core will not reschedule the event. Returning -EAGAIN here would cause
+ * the perf core to re-queue the event and call pmu->add() again on every
+ * context switch, flooding the hypervisor with H_HTM_OP_CONFIGURE hcalls
+ * exactly when it has requested a long backoff delay.
+ */
+ do {
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype,
+ H_HTM_OP_CONFIGURE, param1, param2, 0);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ if (ret <= 0)
+ return -ENODEV;
+
+ /*
+ * htm_event_init() allocated event->pmu_private (struct htm_target_id)
+ * and set event->destroy = reset_htm_active to free it on teardown.
+ * Initialise the tracing state and hw.state before calling start.
+ */
+ target->tracing_active = HTM_TRACING_INACTIVE;
+ event->hw.state = PERF_HES_STOPPED;
+
+ /*
+ * Start tracing via the .start callback so the standard
+ * PERF_EF_START / ioctl(ENABLE) path is honoured.
+ */
+ if (flags & PERF_EF_START) {
+ htm_event_start(event, 0); /* flags=0: not a context switch */
+ if (target->tracing_active == HTM_TRACING_INACTIVE) {
+ /*
+ * Start failed. Attempt to deconfigure to avoid leaving
+ * the hypervisor resource permanently reserved.
+ * pmu->add() is atomic, only H_BUSY is retried. If
+ * H_LONG_BUSY_* or other eerros is returned here, the
+ * resource cannot be reclaimed in this context; log the
+ * failure so it is visible in the kernel log.
+ */
+ retries = 0;
+ do {
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex,
+ cfg.nodalchipindex, cfg.coreindexonchip,
+ cfg.htmtype, H_HTM_OP_DECONFIGURE, 0, 0, 0);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+ if (ret <= 0) {
+ pr_err("DECONFIGURE failed in htm event add (ret=%d) node:%u chip:%u core:%u type:%u;\n",
+ ret, cfg.nodeindex, cfg.nodalchipindex, cfg.coreindexonchip, cfg.htmtype);
+ }
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static void htm_event_del(struct perf_event *event, int flags)
+{
+ int rc, ret, retries = 0;
+ struct htm_target_id *target = event->pmu_private;
+ struct htm_config cfg = target->cfg;
+
+ /*
+ * pmu->del() is called by the perf core after pmu->stop(), whether
+ * triggered by ioctl(PERF_EVENT_IOC_DISABLE) or event destruction.
+ * pmu->del() is invoked in an atomic context with IRQs disabled,
+ * sleeping is impossible.
+ *
+ * If a prior htm_event_stop() call returned with tracing_active still
+ * set to HTM_TRACING_ACTIVE (because H_LONG_BUSY_* caused an immediate
+ * exit), calling htm_event_stop() again here with flags=0 retries the
+ * H_HTM_OP_STOP hcall. This del path is the guaranteed retry point:
+ * the perf core will always reach del after stop, so the trace is not
+ * permanently left running in the hypervisor. Only H_BUSY is retried
+ * here; H_LONG_BUSY_* or any erroes on stop is treated as a best-effort,
+ * ie the deconfigure that follows will still be attempted and error logged.
+ */
+ htm_event_stop(event, 0);
+
+ /*
+ * Deconfigure the hardware resource. Only H_BUSY is retried.
+ * If H_LONG_BUSY_* or any other error is returned, the resource
+ * cannot be reclaimed in this atomic context; log the failure so it
+ * is visible in the kernel log.
+ */
+ do {
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype,
+ H_HTM_OP_DECONFIGURE, 0, 0, 0);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+ if (ret <= 0) {
+ pr_err("DECONFIGURE failed in htm event del (ret=%d) node:%u chip:%u core:%u type:%u;\n",
+ ret, cfg.nodeindex, cfg.nodalchipindex, cfg.coreindexonchip, cfg.htmtype);
+ }
+ /* pmu_private freed by event->destroy = reset_htm_active */
+}
+
+static void htm_event_read(struct perf_event *event)
+{
+}
+
+static struct pmu htm_pmu = {
+ .task_ctx_nr = perf_invalid_context,
+ .name = "htm",
+ .attr_groups = attr_groups,
+ .event_init = htm_event_init,
+ .add = htm_event_add,
+ .del = htm_event_del,
+ .read = htm_event_read,
+ .start = htm_event_start,
+ .stop = htm_event_stop,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_EXCLUSIVE,
+};
+
+static int htm_init(void)
+{
+ int r;
+
+ if (!firmware_has_feature(FW_FEATURE_LPAR)) {
+ pr_debug("Only supported on LPAR platforms running under a Hypervisor\n");
+ return -ENODEV;
+ }
+
+ if (is_kvm_guest()) {
+ pr_debug("Only supported for L1 host system\n");
+ return -ENODEV;
+ }
+
+ r = perf_pmu_register(&htm_pmu, htm_pmu.name, -1);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+device_initcall(htm_init);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH V3 2/6] powerpc/perf: Reject duplicate HTM target reservations
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
HTM tracing is controlled through hypervisor calls and operates on a
system-scoped target identified by HTM type, node index, chip index,
and core index.
HTM events must be opened with cpu=N to pin the AUX buffer file
descriptor to a specific CPU. The intended usage is:
perf record -e htm/nodeindex=0,nodalchipindex=2,htm_type=1,cpu=8/ ...
With cpu=N, the event is opened on exactly one CPU and
perf_event_open() is called once for that event. Multiple HTM events
for different targets (node/chip/core tuples) may be opened
simultaneously on different CPUs.
However, two independent perf_event_open() calls can still target the
same (node, chip, core, type) tuple from different CPUs or processes.
Without driver-side target tracking, both opens succeed htm_event_init()
independently and both proceed to htm_event_add(), where they issue
duplicate H_HTM_OP_CONFIGURE and H_HTM_OP_START hcalls against the same
hardware resource. This causes conflicts in the underlying H_HTM
operations.
Track reserved HTM targets globally and reject duplicate reservations
for the same target. The reservation is created during htm_event_init()
and released through the event destroy path. This prevents concurrent
duplicate opens of the same hardware target while still allowing
different targets to be used simultaneously on different CPUs.
Returning -EBUSY from htm_event_init() for a duplicate open is
intentional and correct. A user who mistakenly opens the same HTM
target twice (or runs perf record without cpu=N, causing every online
CPU to attempt an open of the same target) receives a clear "PMU
counters are busy" error from the perf tool, directing them to add the
required cpu=N qualifier. Opening the same HTM node/chip/core target
from multiple CPUs simultaneously has no meaningful purpose: HTM
hardware tracing operates on the target itself, not on the CPU that
issued the hcall.
Extend the existing per-event htm_target_id structure with a list node,
and use the stored htm_config in pmu_private for target comparison.
A cpumask-based approach was considered but not used: cpumask restricts
which CPUs an event may be opened on, but HTM operates on a hardware
target (node/chip/core) that is independent of the CPU opening the
event. A user may open an HTM event for a specific node/chip/core
target from any CPU in the system, not just CPUs that belong to that
node. A cpumask would therefore either over-restrict valid opens or
require a per-target mask that mirrors the target list anyway. The
approach in this patch handles the real constraint: the same hardware
target cannot be configured twice, regardless of which CPU does the
event open.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Commit message rewritten to clarify the intended usage model:
HTM events must be opened with cpu=N to pin the AUX buffer fd to
a specific CPU. V2 framed the problem as a system-wide perf record
-a race; V3 makes the cpu=N requirement and the explicit duplicate
open scenario the primary motivation.
- Added explanation that -EBUSY from htm_event_init() is intentional:
the user receives a clear "PMU counters are busy" error directing
them to add cpu=N.
- No functional change to the driver code in this patch.
Changes in V2:
- New patch. V1 did not protect against concurrent duplicate opens of
the same HTM target when 'perf record -a' initialises system-wide
events in parallel on all CPUs.
- Adds a global reserved-targets list. htm_event_init() rejects any
open whose (node, chip, core, type) tuple is already reserved; the
reservation is released through the event destroy path.
- Different targets can still be opened simultaneously on different CPUs.
arch/powerpc/perf/htm-perf.c | 42 ++++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index 4e4c924ecfd0..84a5601ee7f7 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -77,9 +77,13 @@ struct htm_config {
* htm_event_start() and htm_event_stop() to make hcall decisions.
* event->hw.state is kept in sync for the perf core only.
*/
+static LIST_HEAD(htm_active_targets_list);
+static DEFINE_MUTEX(htm_targets_lock);
+
struct htm_target_id {
struct htm_config cfg;
int tracing_active; /* HTM_TRACING_ACTIVE / HTM_TRACING_INACTIVE */
+ struct list_head list;
};
/* Helper to parse the 28-bit event config into distinct fields */
@@ -155,7 +159,17 @@ static ssize_t htm_return_check(int rc)
static void reset_htm_active(struct perf_event *event)
{
- kfree(event->pmu_private);
+ struct htm_target_id *target = event->pmu_private;
+
+ if (!target)
+ return;
+
+ mutex_lock(&htm_targets_lock);
+ if (!list_empty(&target->list))
+ list_del(&target->list);
+ mutex_unlock(&htm_targets_lock);
+
+ kfree(target);
event->pmu_private = NULL;
}
@@ -163,6 +177,7 @@ static int htm_event_init(struct perf_event *event)
{
u64 config = event->attr.config;
struct htm_config cfg;
+ struct htm_target_id *target, *tmp;
if (event->attr.inherit)
return -EOPNOTSUPP;
@@ -189,11 +204,30 @@ static int htm_event_init(struct perf_event *event)
}
/* Allocate per-event private state; freed via event->destroy */
- event->pmu_private = kzalloc(sizeof(struct htm_target_id), GFP_KERNEL);
- if (!event->pmu_private)
+ target = kzalloc(sizeof(*target), GFP_KERNEL);
+ if (!target)
return -ENOMEM;
- ((struct htm_target_id *)event->pmu_private)->cfg = cfg;
+ target->cfg = cfg;
+ target->tracing_active = HTM_TRACING_INACTIVE;
+ INIT_LIST_HEAD(&target->list);
+
+ mutex_lock(&htm_targets_lock);
+ list_for_each_entry(tmp, &htm_active_targets_list, list) {
+ if (tmp->cfg.htmtype == cfg.htmtype &&
+ tmp->cfg.nodeindex == cfg.nodeindex &&
+ tmp->cfg.nodalchipindex == cfg.nodalchipindex &&
+ tmp->cfg.coreindexonchip == cfg.coreindexonchip) {
+ mutex_unlock(&htm_targets_lock);
+ kfree(target);
+ return -EBUSY;
+ }
+ }
+
+ list_add_tail(&target->list, &htm_active_targets_list);
+ mutex_unlock(&htm_targets_lock);
+
+ event->pmu_private = target;
event->destroy = reset_htm_active;
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
Implement support for auxiliary (AUX) ring buffers in the HTM PMU driver.
This enables high-volume trace data to be streamed directly into a perf
AUX buffer for deferred post-processing by the perf tool.
Introduce the PMU data structures htm_pmu_buf and htm_pmu_ctx, and
implement the core lifecycle hooks:
htm_setup_aux(): Allocates per-CPU context and records the AUX buffer
base, head, and size for the active trace window.
htm_free_aux(): Releases the PMU-private tracking allocations.
htm_pmu_ctx embeds a struct perf_output_handle as a per-CPU variable,
following the established upstream pattern. Reentrancy
is handled by perf_aux_output_begin() itself via rb->aux_nest: a nested
caller (e.g. from NMI context) receives NULL and returns immediately
without touching the outer handle.
Physical contiguity requirement:
The perf core AUX allocator (rb_alloc_aux) may return a page array with
physical fragmentation gaps. H_HTM_OP_DUMP_DATA operates on raw
physical addresses and requires a strictly contiguous region; crossing
a gap would cause silent memory corruption.
To prevent this, opt into PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
to request large, physically contiguous allocations, and perform a
page-by-page physical continuity scan in htm_event_read() before each
H_HTM_OP_DUMP_DATA call. The scan verifies that
virt_to_phys(page[n]) + PAGE_SIZE == virt_to_phys(page[n+1]) and
breaks the loop on the first discontinuity, ensuring the dump to the
verified safe window.
htm_dump_sample_data() returns the record count directly (already
divided by the per-format record size) so htm_event_read() uses the
value without format knowledge:
- AUX trace path: chunk_size / 128 (128-byte HTM records)
- Memory cfg path: to_copy / 32 (32-byte entries, patch 4)
htm_event_read() sets event->count as follows:
- ret > 0: record count written; used directly as event->count.
- ret == -ENOSPC (AUX ring buffer full): event->count = 1 so the
perf tool drain loop keeps retrying after the consumer drains the
buffer.
- all other non-success paths: event->count = 0 so the drain loop
stops cleanly.
event->count does not represent an instruction or cycle count; actual
trace records are decoded in userspace by the perf tool.
HTM target identity and tracing state are kept in event->pmu_private
(htm_target_id). AUX-private state holds only buffer metadata and
dump progress; htm_target_id.tracing_active remains the source for
tracing state.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Added a dedicated Physical contiguity requirement section and a full
per-condition htm_dump_sample_data() return values and event->count
table in the commit body, covering all five exit paths (success,
-ENOSPC, H_NOT_AVAILABLE, H_LONG_BUSY_*/hard error, stop-failed
before dump). V2 had a brief paragraph.
- htm_dump_sample_data() return type changed from int to ssize_t; ret
locals in htm_dump_sample_data() and htm_event_read() likewise changed
to ssize_t. Prevents truncation of the upper 32 bits of chunk_size on
64-bit PowerPC.
- Success path now returns (ssize_t)(chunk_size / 128), the number
128-byte HTM trace records, rather than raw chunk_size bytes.
htm_event_read() uses the value directly (local64_set(&event->count,
ret)) without any further division, keeping it free of format
knowledge. The same contract is extended to the memory-config path
in patch 4 (to_copy / 32).
- Removed the unused trace_records field from struct htm_pmu_buf and
its initialisation and increment sites. V2 carried the field as
dead code.
- Added event->count does not represent an instruction or cycle count
sentence to both the commit body and the in-code comment
in htm_event_read().
Changes in V2:
- Opted into PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE to
request physically contiguous allocations, which H_HTM_OP_DUMP_DATA
requires. V1 had no contiguity requirement and could silently
corrupt memory if the allocator returned a fragmented page array.
- Added a page-by-page physical-continuity scan in htm_event_read()
before every H_HTM_OP_DUMP_DATA call. The scan verifies that
virt_to_phys(page[n]) + PAGE_SIZE == virt_to_phys(page[n+1]) and
stops at the first gap, bounding the dump to a verified safe window.
- htm_dump_sample_data() now returns a record count (already divided
by the per-format record size) rather than raw bytes, so
htm_event_read() uses the value directly without format knowledge.
AUX trace path returns chunk_size / 128; the memory config path
added in patch 4 will return to_copy / 32. Three-way semantics:
ret > 0 -> count = ret; ret == -ENOSPC -> count = 1; otherwise
count = 0. The V1 arch_record__collect_final_data callback loop
is replaced by this mechanism.
- Removed the unused trace_records field from htm_pmu_buf; htm_dump_
sample_data() now returns chunk_size on success so htm_event_read()
can derive the record count directly.
- Introduced distinct htm_pmu_buf and htm_pmu_ctx structures; V1 used a
single struct htm_pmu_buf for both purposes.
arch/powerpc/perf/htm-perf.c | 250 ++++++++++++++++++++++++++++++++++-
1 file changed, 249 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index 84a5601ee7f7..f880a5fc8833 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -95,6 +95,22 @@ static inline void parse_htm_config(u64 config, struct htm_config *cfg)
cfg->coreindexonchip = (config >> 20) & 0xff;
}
+struct htm_pmu_buf {
+ int nr_pages;
+ bool snapshot;
+ void *base;
+ void **pages;
+ u64 head;
+ u64 size;
+ int collect_htm_trace;
+};
+
+struct htm_pmu_ctx {
+ struct perf_output_handle handle;
+};
+
+static DEFINE_PER_CPU(struct htm_pmu_ctx, htm_pmu_ctx);
+
/*
* Check the return code for H_HTM hcall.
* Return 1 if either H_PARTIAL or H_SUCCESS is returned.
@@ -415,8 +431,237 @@ static void htm_event_del(struct perf_event *event, int flags)
/* pmu_private freed by event->destroy = reset_htm_active */
}
+static ssize_t htm_dump_sample_data(struct perf_event *event)
+{
+ struct htm_pmu_ctx *htm_ctx = this_cpu_ptr(&htm_pmu_ctx);
+ struct htm_target_id *target = event->pmu_private;
+ struct htm_pmu_buf *aux_buf;
+ struct htm_config cfg = target->cfg;
+ u64 chunk_size, dump_offset, page_index, page_offset;
+ u64 max_contiguous_bytes, expected_phys, scan_index, actual_phys;
+ u64 hypervisor_target_phys;
+ void *target_page_virt;
+ ssize_t ret = 0;
+ int retries = 0;
+ long rc;
+
+ /* Start AUX transaction session framework */
+ aux_buf = perf_aux_output_begin(&htm_ctx->handle, event);
+ if (!aux_buf)
+ return 0;
+
+ if (!aux_buf->collect_htm_trace) {
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return 0;
+ }
+
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ htm_event_stop(event, 0);
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ /*
+ * H_HTM_OP_STOP failed.
+ * Cannot dump data while the trace is still running.
+ * Return -EIO to signal a non-retriable hardware failure;
+ * htm_event_read() will set event->count=0 and the drain
+ * loop will stop cleanly.
+ */
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return -EIO;
+ }
+ }
+
+ /* Derive the exact target destination point directly out of active ring pointers */
+ dump_offset = htm_ctx->handle.head & (aux_buf->size - 1);
+ page_index = dump_offset >> PAGE_SHIFT;
+ page_offset = dump_offset & (PAGE_SIZE - 1);
+
+ /*
+ * Assess constraints regarding space remaining across the mapping
+ * context boundary
+ */
+ chunk_size = htm_ctx->handle.size;
+ chunk_size &= PAGE_MASK;
+
+ if (chunk_size > (aux_buf->size - dump_offset))
+ chunk_size = aux_buf->size - dump_offset;
+
+ /*
+ * HTM driver uses these capabilities:
+ * PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
+ * the core perf ring-buffer allocator (rb_alloc_aux) tries to allocate
+ * physically contiguous page block. If not available, it tries to allocate
+ * largest possible contiguous block.
+ *
+ * Example: If we ask perf for 1024 pages (64MB), the kernel executes a loop
+ * inside rb_alloc_aux() to fulfill that request using the buddy allocator. it
+ * always tries to grab the largest possible contiguous block of memory it can
+ * find first, then takes the next largest, and repeats until request is
+ * completely filled. Here while writing to aux buffer, to eliminate any
+ * virtual or physical boundary overruns, check for the page boundary.
+ *
+ * Dynamically scan forward page-by-page from our active page_index to
+ * calculate the absolute boundary limit of this current physically
+ * contiguous block chunk. Prevents hypervisor macro overruns across
+ * asymmetrical fragmentation gaps.
+ */
+ max_contiguous_bytes = PAGE_SIZE - page_offset;
+ scan_index = page_index + 1;
+ expected_phys = (u64)virt_to_phys(aux_buf->pages[page_index]) + PAGE_SIZE;
+
+ while (scan_index < aux_buf->nr_pages && max_contiguous_bytes < chunk_size) {
+ actual_phys = (u64)virt_to_phys(aux_buf->pages[scan_index]);
+
+ if (actual_phys != expected_phys)
+ break; /* Intersected a fragmentation boundary block link! */
+
+ max_contiguous_bytes += PAGE_SIZE;
+ expected_phys += PAGE_SIZE;
+ scan_index++;
+ }
+
+ /* Bound transfer length tightly within the validated contiguous window */
+ if (chunk_size > max_contiguous_bytes)
+ chunk_size = max_contiguous_bytes;
+
+ if (!chunk_size) {
+ /*
+ * No space in the AUX ring buffer right now. Leave
+ * collect_htm_trace set. Return -ENOSPC so htm_event_read()
+ * keeps event->count non-zero, signalling to the caller that
+ * collection is still ongoing and another pmu->read pass
+ * should be attempted once the consumer has drained the buffer.
+ */
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return -ENOSPC;
+ }
+
+ /*
+ * Compute the precise base target address using
+ * localized page offset rules
+ */
+ target_page_virt = aux_buf->pages[page_index];
+ hypervisor_target_phys = (u64)virt_to_phys(target_page_virt) + page_offset;
+
+ do {
+ /*
+ * Invoke H_HTM call with:
+ * - operation as htm dump (H_HTM_OP_DUMP_DATA)
+ * - last three values are address, size and offset
+ */
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype, H_HTM_OP_DUMP_DATA,
+ hypervisor_target_phys, chunk_size, aux_buf->head);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ if (ret > 0) {
+ aux_buf->head += chunk_size;
+ perf_aux_output_end(&htm_ctx->handle, chunk_size);
+ /*
+ * Return the number of 128-byte HTM trace records written.
+ * Dividing here keeps htm_event_read() free of format
+ * knowledge: it can simply use the returned count directly,
+ * regardless of which data path (AUX trace or memory config)
+ * produced it.
+ */
+ return (ssize_t)(chunk_size / 128);
+ }
+
+ /*
+ * Hcall failed. All non-success paths end collection for this
+ * buffer session.
+ */
+ aux_buf->collect_htm_trace = 0;
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return ret;
+}
+
static void htm_event_read(struct perf_event *event)
{
+ ssize_t ret;
+
+ ret = htm_dump_sample_data(event);
+ /*
+ * htm_dump_sample_data() returns the record count directly
+ * (already divided by the per-format record size):
+ * AUX trace path: chunk_size / 128 (128-byte HTM records)
+ * Memory cfg path: to_copy / 32 (32-byte entries)
+ *
+ * ret > 0: record count written; use directly as event->count.
+ * ret == -ENOSPC: AUX buffer full, hypervisor stream intact;
+ * count = 1 so the drain loop keeps retrying
+ * once the consumer has drained the buffer.
+ * ret <= 0: EOF, stop failed, or hard error; count = 0
+ * so the drain loop stops cleanly.
+ *
+ * event->count does not represent an instruction or cycle count;
+ * actual trace records are decoded in userspace by the perf tool.
+ */
+ if (ret > 0)
+ local64_set(&event->count, ret);
+ else if (ret == -ENOSPC)
+ local64_set(&event->count, 1);
+ else
+ local64_set(&event->count, 0);
+}
+
+/*
+ * Set up pmu-private data structures for an AUX area
+ * **pages contains the aux buffer allocated for this event
+ * for the corresponding cpu. rb_alloc_aux uses "alloc_pages_node"
+ * and returns pointer to each page address.
+ * PMU capabilities: PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
+ * to try get closest possible physically contiguous page blocks.
+ *
+ * The aux private data structure ie, "struct htm_pmu_buf" mainly
+ * saves
+ * - buf->base: aux buffer base address
+ * - buf->head: offset from base address where data will be written to.
+ * - buf->size: Size of allocated memory
+ */
+static void *htm_setup_aux(struct perf_event *event, void **pages,
+ int nr_pages, bool snapshot)
+{
+ int cpu = event->cpu;
+ struct htm_pmu_buf *buf;
+
+ if (!nr_pages)
+ return NULL;
+
+ if (cpu == -1)
+ cpu = raw_smp_processor_id();
+
+ buf = kzalloc_node(sizeof(*buf), GFP_KERNEL, cpu_to_node(cpu));
+ if (!buf)
+ return NULL;
+
+ buf->nr_pages = nr_pages;
+ buf->snapshot = snapshot;
+ buf->size = (u64)nr_pages << PAGE_SHIFT;
+ buf->pages = pages;
+
+ buf->base = pages[0];
+ if (!buf->base) {
+ kfree(buf);
+ return NULL;
+ }
+
+ buf->collect_htm_trace = 1;
+ buf->head = 0;
+ return buf;
+}
+
+/*
+ * free pmu-private AUX data structures
+ */
+static void htm_free_aux(void *aux)
+{
+ struct htm_pmu_buf *buf = aux;
+
+ if (!buf)
+ return;
+
+ kfree(buf);
}
static struct pmu htm_pmu = {
@@ -429,7 +674,10 @@ static struct pmu htm_pmu = {
.read = htm_event_read,
.start = htm_event_start,
.stop = htm_event_stop,
- .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_EXCLUSIVE,
+ .setup_aux = htm_setup_aux,
+ .free_aux = htm_free_aux,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_EXCLUSIVE
+ | PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE,
};
static int htm_init(void)
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH V3 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (2 preceding siblings ...)
2026-07-25 6:59 ` [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
The H_HTM hypervisor call can also return the system memory
configuration, which describes the physical to logical real address
mapping for logical partitions.
After dumping HTM trace data into the AUX buffer, capture the
corresponding HTM system memory configuration records and emit them as
raw perf sample data for userspace parsing.
Add AUX-private tracking for a hypervisor memory-configuration dump
buffer, a stable emit buffer, and iterator state. Once AUX trace dumping
completes, htm_event_read() switches to H_HTM_OP_DUMP_SYSMEM_CONF and
iterates over the returned records until the configuration stream is
exhausted.
Trace payload continues to be written to the AUX buffer, while memory
configuration records are emitted as raw perf samples. This keeps the
AUX stream focused on trace data and allows the configuration records to
be decoded separately in userspace.
The hypervisor fills as many 32-byte entries as fit within the buffer
size passed to H_HTM_OP_DUMP_SYSMEM_CONF — it does not cap at a fixed
entry count. Observed maximum fill is 64480 bytes (2015 entries at
32 bytes each plus a 32-byte header). HTM_MEM_BUF_SIZE is therefore
defined as the allocation size (65440 bytes) and HTM_MEM_MAX_ENTRIES is
derived from it ((HTM_MEM_BUF_SIZE - 32) / 32 = 2043), not the other
way around. This ensures the hcall is always told the true buffer size
and the WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE) guard is a genuine
impossibility check rather than a post-overflow assertion.
HTM_MEM_BUF_SIZE = 65440 is the largest multiple of 32 that satisfies
both constraints: it exceeds the observed 64480-byte maximum fill by 960
bytes of headroom, and the resulting perf record (65440 + 92 bytes of
fixed overhead = 65532) stays below 65535, the __u16 limit of
perf_event_header.size. The 92-byte overhead is: 8 (perf_event_header)
+ 64 (header_size worst case: 8 u64 sample fields) + 16 (id_header_size
worst case) + 4 (PERF_SAMPLE_RAW u32 size prefix).
perf_fetch_caller_regs() is used to initialise the pt_regs argument
passed to perf_event_overflow(). An uninitialised stack frame would
leak kernel stack bytes to userspace if the event is opened with
PERF_SAMPLE_REGS_INTR. This follows the pattern used by tracepoints
and BPF perf-event helpers for synthetic sample emission.
When perf_event_overflow() throttles the event (returns non-zero), the
iterator mem_start is not advanced. The same memory configuration block
will be retried on the next htm_event_read() call once the event is
unthrottled. collect_htm_mem is left set so the retry path is entered;
ENOSPC is returned so event->count is set to 1 and the drain loop
keeps retrying until the ring buffer consumer catches up.
Keep HTM tracing state in event->pmu_private via htm_target_id. AUX
private state is used only for dump progress and staging buffers.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Fixed HTM_MEM_BUF_SIZE defined as (32 + 2013 * 32 = 64448 bytes) while
the hypervisor actually fills up to 64480 bytes (2015 entries) when
given a sufficiently large buffer — overflowing the allocation by 32
bytes. The hypervisor fills as many entries as fit within the given
buffer size; it does not cap at a fixed entry count. Fixed by
redefining HTM_MEM_BUF_SIZE as 65440U (the largest multiple of 32
fitting in perf_event_header.size __u16 with 92 bytes of worst-case
header overhead: 65440 + 92 = 65532 < 65535) and deriving
HTM_MEM_MAX_ENTRIES from it ((HTM_MEM_BUF_SIZE - 32) / 32 = 2043).
The buffer now covers the observed maximum with 960 bytes headroom,
and the WARN_ON_ONCE guard is a genuine impossibility check.
- Fixed htm_mem_buf allocated as PAGE_SIZE (4096 bytes on 4K-page
configs) while the hypervisor was told the buffer length is
HTM_MEM_BUF_SIZE. Changed to kmalloc_node with HTM_MEM_BUF_SIZE.
- Fixed uninitialized struct pt_regs regs passed to perf_event_overflow().
If the event is opened with PERF_SAMPLE_REGS_INTR the perf core reads
these bytes into the ring buffer, leaking kernel stack memory to
userspace. Added perf_fetch_caller_regs(®s) after the declaration
block, following the pattern in kernel/trace/trace_event_perf.c and
kernel/trace/bpf_trace.c.
- Clarified the perf_event_overflow() throttle path: when the event is
throttled, mem_start is intentionally not advanced (the same block will
be retried on the next drain pass once unthrottled). Added a comment
making this explicit and distinguishing it from the error/EOF paths
that clear collect_htm_mem.
Changes in V2:
- Memory configuration records are now emitted as PERF_SAMPLE_RAW
samples directly by htm_event_read() after the AUX trace dump
completes, using H_HTM_OP_DUMP_SYSMEM_CONF. V1 embedded the
configuration data inside the AUX buffer itself and used two
PERF_SAMPLE_RAW boundary markers (start/end) to delimit it.
- The two-marker boundary scheme is removed. There is no longer any
interleaving of memory configuration data inside the AUX stream;
the AUX buffer carries only bus-trace data.
- Separate AUX-private fields for a hypervisor dump buffer, a stable
emit buffer, and iterator state are introduced to manage the
multi-call SYSMEM_CONF dump loop.
- Tracing state remains in event->pmu_private (htm_target_id); AUX
private state is used only for dump progress and staging buffers,
consistent with the restructuring in patches 1 and 3.
arch/powerpc/perf/htm-perf.c | 205 ++++++++++++++++++++++++++++++++++-
1 file changed, 204 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index f880a5fc8833..af9a8412ff20 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -103,6 +103,10 @@ struct htm_pmu_buf {
u64 head;
u64 size;
int collect_htm_trace;
+ void *htm_mem_buf; /* Staging bounce area allocated node-locally */
+ void *emit_buf; /* Stable buffer for perf raw sample emission */
+ u64 mem_start; /* Hypervisor offset state iterator tracker */
+ int collect_htm_mem; /* State flag tracking whether memory logging is ongoing */
};
struct htm_pmu_ctx {
@@ -173,6 +177,174 @@ static ssize_t htm_return_check(int rc)
#define HTM_TRACING_ACTIVE 1
#define HTM_TRACING_INACTIVE 0
+/*
+ * HTM_MEM_BUF_SIZE is the allocation size for the hcall staging buffer.
+ * The hypervisor fills as many 32-byte entries as fit within the buffer
+ * size passed to H_HTM_OP_DUMP_SYSMEM_CONF — it does not cap at a fixed
+ * entry count. Observed maximum fill is 64480 bytes (2015 entries).
+ *
+ * HTM_MEM_BUF_SIZE is chosen to satisfy two hard constraints:
+ *
+ * 1. Must cover the observed maximum fill:
+ * HTM_MEM_BUF_SIZE >= 64480 (2015 * 32 + 32-byte header)
+ *
+ * 2. The full perf record (perf_event_header + fixed sample fields +
+ * PERF_SAMPLE_RAW u32 size prefix + to_copy) must fit in
+ * perf_event_header.size which is __u16 (max 65535):
+ * overhead = 8 (perf_event_header)
+ * + 64 (header_size, worst case: 8 u64 sample fields)
+ * + 16 (id_header_size, worst case)
+ * + 4 (PERF_SAMPLE_RAW u32 size prefix)
+ * = 92 bytes
+ * to_copy <= 65535 - 92 = 65443
+ * round down to multiple of 32: 65440
+ *
+ * 3. HTM_MEM_BUF_SIZE must be a multiple of 32 so a whole number of
+ * 32-byte entries fill it exactly.
+ *
+ * 65440 = 32 + 2043 * 32 is the largest multiple of 32 satisfying all
+ * three constraints:
+ * - covers 64480 with 960 bytes headroom
+ * - total record: 65440 + 92 = 65532 < 65535 (3-byte u16 margin)
+ *
+ * HTM_MEM_MAX_ENTRIES is derived from HTM_MEM_BUF_SIZE — not the other
+ * way around — so the hcall is always given the true buffer size and
+ * the WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE) guard is a genuine
+ * impossibility check rather than a post-overflow assertion.
+ */
+#define HTM_MEM_BUF_SIZE 65440U
+#define HTM_MEM_MAX_ENTRIES ((HTM_MEM_BUF_SIZE - 32) / 32) /* 2043 */
+
+/*
+ * htm_collect_memory_config - drain H_HTM_OP_DUMP_SYSMEM_CONF into the
+ * perf ring buffer as PERF_SAMPLE_RAW records.
+ *
+ * Returns the number of 32-byte memory configuration entries emitted
+ * (to_copy / 32) on success, 0 if the event was throttled or the stream
+ * ended normally, or a negative error code on hard failure. The caller
+ * (htm_dump_sample_data) uses the return value directly as event->count,
+ * consistent with the AUX trace path returning chunk_size / 128.
+ */
+static ssize_t htm_collect_memory_config(struct perf_event *event,
+ struct htm_pmu_buf *aux_buf)
+{
+ struct perf_sample_data data;
+ struct perf_raw_record raw;
+ struct pt_regs regs;
+ u8 *htm_mem_buf = aux_buf->htm_mem_buf;
+ __be64 *num_entries;
+ u64 next_start;
+ u64 to_copy;
+ void *emit_buf = aux_buf->emit_buf;
+ long rc;
+ ssize_t ret = 0;
+ int retries;
+
+ /*
+ * Initialise regs to the current caller context. perf_event_overflow()
+ * may sample register state into the ring buffer if the event was
+ * opened with PERF_SAMPLE_REGS_INTR; an uninitialised stack frame
+ * would leak kernel stack bytes to userspace. Use
+ * perf_fetch_caller_regs() to capture a safe, deterministic snapshot
+ * of the current CPU state — the same pattern used by tracepoints and
+ * BPF perf-event helpers for synthetic sample emission.
+ */
+ perf_fetch_caller_regs(®s);
+
+ /*
+ * htm_mem_buf is the hcall memory buffer target reused each iteration.
+ * emit_buf is a preallocated stable buffer used for perf raw
+ * sample emission, so raw.frag.data remains valid during
+ * perf_event_overflow().
+ * Size: 32-byte header + HTM_MEM_MAX_ENTRIES * 32-byte entries.
+ */
+ while (true) {
+ retries = 0;
+ do {
+ rc = htm_hcall_wrapper(htmflags, 0, 0, 0,
+ 0, H_HTM_OP_DUMP_SYSMEM_CONF,
+ virt_to_phys(aux_buf->htm_mem_buf),
+ HTM_MEM_BUF_SIZE, aux_buf->mem_start);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ /*
+ * ret == 0 (H_NOT_AVAILABLE): normal end of stream — clear
+ * collect_htm_mem so the next read does not re-enter.
+ * ret < 0 (error): hard failure — clear collect_htm_mem and
+ * stop.
+ * Both cases are treated as "no more data for this session".
+ */
+ if (ret <= 0) {
+ aux_buf->collect_htm_mem = 0;
+ break;
+ }
+
+ /*
+ * Read next iterator value from the hcall response BEFORE
+ * emitting — but only commit it to mem_start after a
+ * successful write of raw data.
+ */
+ next_start = be64_to_cpu(*((__be64 *)(htm_mem_buf + 0x8)));
+
+ /*
+ * The hcall was given HTM_MEM_BUF_SIZE bytes of buffer space,
+ * so num_entries is already bounded to HTM_MEM_MAX_ENTRIES.
+ * The response is a complete hcall data:
+ * [32-byte header][num_entries * 32-byte entries]
+ * Userspace can validate and parse it directly.
+ */
+ num_entries = (__be64 *)(htm_mem_buf + 0x10);
+ to_copy = 32 + (be64_to_cpu(*num_entries) * 32);
+ if (WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE)) {
+ ret = -EIO;
+ aux_buf->collect_htm_mem = 0;
+ break;
+ }
+
+ memcpy(emit_buf, aux_buf->htm_mem_buf, to_copy);
+
+ perf_sample_data_init(&data, 0, event->hw.last_period);
+ memset(&raw, 0, sizeof(raw));
+ raw.frag.data = emit_buf;
+ raw.frag.size = to_copy;
+ perf_sample_save_raw_data(&data, event, &raw);
+
+ if (perf_event_overflow(event, &data, ®s)) {
+ /*
+ * Event throttled: the record was not written to the
+ * ring buffer. Do NOT advance mem_start — the same
+ * block will be retried on the next htm_event_read()
+ * call once the event is unthrottled. Leave
+ * collect_htm_mem set so the retry path is entered.
+ * Return -ENOSPC so htm_event_read() sets event->count=1,
+ * keeping the drain loop alive until the ring buffer
+ * consumer catches up.
+ */
+ ret = -ENOSPC;
+ break;
+ }
+
+ /* Record written successfully: advance the iterator */
+ aux_buf->mem_start = next_start;
+
+ /*
+ * Return the number of 32-byte memory configuration entries
+ * in this batch (to_copy / 32). Dividing here keeps
+ * htm_event_read() free of format knowledge, consistent with
+ * the AUX trace path returning chunk_size / 128.
+ */
+ ret = (ssize_t)(to_copy / 32);
+
+ if (!next_start) {
+ aux_buf->collect_htm_mem = 0;
+ break;
+ }
+ }
+
+ return ret;
+}
+
static void reset_htm_active(struct perf_event *event)
{
struct htm_target_id *target = event->pmu_private;
@@ -450,7 +622,7 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
if (!aux_buf)
return 0;
- if (!aux_buf->collect_htm_trace) {
+ if (!aux_buf->collect_htm_trace && !aux_buf->collect_htm_mem) {
perf_aux_output_end(&htm_ctx->handle, 0);
return 0;
}
@@ -470,6 +642,11 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
}
}
+ if (!aux_buf->collect_htm_trace) {
+ ret = htm_collect_memory_config(event, aux_buf);
+ goto out;
+ }
+
/* Derive the exact target destination point directly out of active ring pointers */
dump_offset = htm_ctx->handle.head & (aux_buf->size - 1);
page_index = dump_offset >> PAGE_SHIFT;
@@ -572,6 +749,8 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
* buffer session.
*/
aux_buf->collect_htm_trace = 0;
+ ret = htm_collect_memory_config(event, aux_buf);
+out:
perf_aux_output_end(&htm_ctx->handle, 0);
return ret;
}
@@ -646,7 +825,29 @@ static void *htm_setup_aux(struct perf_event *event, void **pages,
return NULL;
}
+ /*
+ * htm_mem_buf is the staging area passed directly to the
+ * H_HTM_OP_DUMP_SYSMEM_CONF hcall. The hypervisor is told the
+ * buffer length is HTM_MEM_BUF_SIZE (65440 bytes); allocate exactly
+ * that amount. See the HTM_MEM_BUF_SIZE comment for the derivation.
+ */
+ buf->htm_mem_buf = kmalloc_node(HTM_MEM_BUF_SIZE, GFP_KERNEL, cpu_to_node(cpu));
+ if (!buf->htm_mem_buf) {
+ kfree(buf);
+ return NULL;
+ }
+
+ buf->emit_buf = kmalloc_node(HTM_MEM_BUF_SIZE, GFP_KERNEL,
+ cpu_to_node(cpu));
+ if (!buf->emit_buf) {
+ kfree(buf->htm_mem_buf);
+ kfree(buf);
+ return NULL;
+ }
+
buf->collect_htm_trace = 1;
+ buf->collect_htm_mem = 1;
+ buf->mem_start = 0;
buf->head = 0;
return buf;
}
@@ -661,6 +862,8 @@ static void htm_free_aux(void *aux)
if (!buf)
return;
+ kfree(buf->emit_buf);
+ kfree(buf->htm_mem_buf);
kfree(buf);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH V3 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (3 preceding siblings ...)
2026-07-25 6:59 ` [PATCH V3 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
Add ABI documentation for the sysfs entries exposed by the "htm" PMU
under /sys/bus/event_source/devices/htm/.
Document the format attribute group, which describes the bit layout of
perf_event_attr.config accepted by the htm PMU:
event config bits 0-27 (composite: all fields combined)
htm_type config bits 0-3 (HTM_CORE=2, HTM_NEST=1)
nodeindex config bits 4-11
nodalchipindex config bits 12-19
coreindexonchip config bits 20-27
Document the events attribute group, which provides named aliases for
the two supported HTM event types. Users can pass these names directly
to perf:
# perf record -e htm/htm_core/ ...
# perf record -e htm/htm_nest/ ...
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Fixed the format/ attribute table: the doc listed only the composite
'event' attribute (config:0-27) but omitted the four individual
format attributes (htm_type, nodeindex, nodalchipindex,
coreindexonchip) that the commit message described and the driver
exposes. Added all four with their correct bit ranges and a
'For example::' block showing htm_core and htm_nest mappings.
- Fixed the events/ example in the commit message: the previous example
used format-group key=value syntax
(htm/nodalchipindex=2,nodeindex=0,htm_type=1/) which conflates the
format group with the events group. The events group provides named
aliases; the correct examples are 'htm/htm_core/' and 'htm/htm_nest/'.
- Fixed grammar: "Supported attribute are" -> "Supported attributes are".
Changes in V2:
- Updated the bit-range descriptions to match the V2 config layout:
htm_type is bits 0-3 (was listed inconsistently in V1), nodeindex
is bits 4:11, nodalchipindex is bits 12:19, coreindexonchip is
bits 20-27.
- Added documentation for the events/ sysfs group which lists named
events users can pass directly to perf. This group was not
documented in V1.
- Patch is now 5/6 instead of 4/5.
.../sysfs-bus-event_source-devices-htm | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-event_source-devices-htm
diff --git a/Documentation/ABI/testing/sysfs-bus-event_source-devices-htm b/Documentation/ABI/testing/sysfs-bus-event_source-devices-htm
new file mode 100644
index 000000000000..1c3e17cb124c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-event_source-devices-htm
@@ -0,0 +1,34 @@
+What: /sys/bus/event_source/devices/htm/format
+Date: June 2026
+Contact: Linux on PowerPC Developer List <linuxppc-dev at lists.ozlabs.org>
+Description: Read-only. Attribute group to describe the magic bits
+ that go into perf_event_attr.config for a particular pmu.
+ (See ABI/testing/sysfs-bus-event_source-devices-format).
+
+ Each attribute under this group defines a bit range of the
+ perf_event_attr.config. Supported attributes are listed
+ below::
+
+ event = "config:0-27" - composite event ID
+ htm_type = "config:0-3" - HTM type (HTM_CORE=2, HTM_NEST=1)
+ nodeindex = "config:4-11" - node index
+ nodalchipindex = "config:12-19" - nodal chip index
+ coreindexonchip = "config:20-27" - core index on chip
+
+ For example::
+
+ htm_core = "htm_type=2"
+ htm_nest = "htm_type=1"
+
+What: /sys/bus/event_source/devices/htm/events
+Date: June 2026
+Contact: Linux on PowerPC Developer List <linuxppc-dev at lists.ozlabs.org>
+Description: Read-only. Attribute group to describe performance monitoring
+ events for the Hardware Trace Macro (HTM) trace. Each attribute
+ in this group describes a single performance monitoring event
+ supported by htm pmu. The name of the file is the name of
+ the event (See ABI/testing/sysfs-bus-event_source-devices-events).
+
+ For example::
+ # perf record -e htm/htm_core/
+ # perf record -e htm/htm_nest/
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH V3 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (4 preceding siblings ...)
2026-07-25 6:59 ` [PATCH V3 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
@ 2026-07-25 6:59 ` Athira Rajeev
5 siblings, 0 replies; 7+ messages in thread
From: Athira Rajeev @ 2026-07-25 6:59 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
Extend Documentation/arch/powerpc/htm.rst with a new section covering
the HTM perf PMU interface.
The added documentation covers:
- How to open HTM events using perf record, including the event
syntax (nodalchipindex, nodeindex, htm_type, cpu=N) and the
required AUX buffer size (-m,256).
- The two output files produced by perf report:
htm.bin.nX.pX.cX raw bus-trace AUX data
translation.nX.pX.cX memory-configuration records
- How to pass the output files to htmdecode for trace decoding.
- Notes on system-wide collection (-a) vs CPU-pinned collection
(cpu=N in event config) and the one-event-per-target PMU
restriction.
The existing debugfs interface documentation is retained unchanged.
A brief cross-reference is added at the top to point readers to the
new perf interface section.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Fixed "After running perf record, the following files are generated":
htm.bin.* and translation.* are written by perf report (which runs
powerpc_htm_process_auxtrace_info), not by perf record. perf record
produces only perf.data. Corrected the Output Files section and the
Complete Workflow example accordingly. Note: powerpc_htm_process_-
auxtrace_info() is implemented in the companion tools/perf patch series
("tools/perf: Add perf tool support for processing powerpc HTM AUXTRACE
records"); the documentation is written against the complete two-series
feature, which is standard practice for kernel+tools PMU submissions.
- Fixed "perf report -D" in the workflow: perf report -D only prints
AUX buffer sizes; it does not produce htm.bin.* or translation.*.
The output files are produced by plain "perf report".
- Added the missing htmdecode usage section (referenced in commit
message and V2 changelog but absent from the doc body).
- Added the missing PMU restriction note: the HTM PMU uses
PERF_PMU_CAP_EXCLUSIVE so only one event per target (node/chip/core)
is allowed; a second event on the same target returns -EBUSY. Also
noted that cpu=N in the event config is the supported way to pin
collection to a CPU, and -a without cpu=N causes -EBUSY from the
kernel (HTM events require cpu=N since the PMU operates on physical
hardware addresses, not per-task context).
- Fixed typo "htmtype" -> "htm_type" in the config description list.
- Fixed grammar "To open the event on a specific cpu can be specified
using" -> "To specify a CPU, include the cpu= parameter".
- Fixed typo "Target code 6" -> "Target core 6".
Changes in V2:
- Added a new perf-interface section to Documentation/arch/powerpc/htm.rst
describing perf record usage, required AUX buffer size (-m,256), the
two output files (htm.bin.nX.pX.cX and translation.nX.pX.cX), and how
to pass them to htmdecode.
- Added notes on system-wide (-a) vs CPU-pinned collection and
the one-event-per-target PMU restriction introduced in patch 2.
- A cross-reference is added at the top of htm.rst pointing readers to
the new perf interface section.
- The existing debugfs interface documentation is retained unchanged.
- Patch is now 6/6 instead of 5/5.
Documentation/arch/powerpc/htm.rst | 158 ++++++++++++++++++++++++++++-
1 file changed, 155 insertions(+), 3 deletions(-)
diff --git a/Documentation/arch/powerpc/htm.rst b/Documentation/arch/powerpc/htm.rst
index fcb4eb6306b1..15e7484f2836 100644
--- a/Documentation/arch/powerpc/htm.rst
+++ b/Documentation/arch/powerpc/htm.rst
@@ -18,9 +18,10 @@ H_HTM is used as an interface for executing Hardware Trace Macro (HTM)
functions, including setup, configuration, control and dumping of the HTM data.
For using HTM, it is required to setup HTM buffers and HTM operations can
be controlled using the H_HTM hcall. The hcall can be invoked for any core/chip
-of the system from within a partition itself. To use this feature, a debugfs
-folder called "htmdump" is present under /sys/kernel/debug/powerpc.
+of the system from within a partition itself.
+To use this feature, a debugfs folder called "htmdump" is present under
+/sys/kernel/debug/powerpc. Another interface is via perf.
HTM debugfs example usage
=========================
@@ -94,7 +95,158 @@ This trace file will contain the relevant instruction traces
collected during the workload execution. And can be used as
input file for trace decoders to understand data.
-Benefits of using HTM debugfs interface
+HTM perf interface usage
+========================
+
+The HTM (Hardware Trace Macro) perf interface enables collection and analysis
+of hardware trace data from PowerPC systems. This interface allows users to
+capture detailed execution traces for performance analysis and debugging.
+
+Event Configuration
+-------------------
+
+Use ``perf record`` with the htm PMU event. The event is configured using
+named parameters that specify the target hardware location and trace type:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Parameter
+ - Description
+ * - htm_type
+ - Type of HTM trace to collect (bits 0-3)
+ * - nodeindex
+ - Node index in the system topology (bits 4-11)
+ * - nodalchipindex
+ - Chip index within the specified node (bits 12-19)
+ * - coreindexonchip
+ - Core index on the specified chip (bits 20-27)
+
+- event: "config:0-27"
+- htm_type: "config:0-3"
+- nodeindex: "config:4-11"
+- nodalchipindex: "config:12-19"
+- coreindexonchip: "config:20-27"
+
+1) nodeindex, nodalchipindex, coreindexonchip: this specifies
+ which partition to configure the HTM for.
+2) htm_type: specifies the type of HTM.
+
+Event Syntax
+------------
+
+The event configuration uses named parameters::
+
+ htm/nodeindex=N,nodalchipindex=C,coreindexonchip=R,htm_type=T/
+
+Opening the event on a specific CPU can be specified::
+
+ htm/nodeindex=N,nodalchipindex=C,coreindexonchip=R,htm_type=T,cpu=x/
+
+Where:
+
+- N = node index
+- C = chip index within the node
+- R = core index on the chip
+- T = HTM type
+- x = CPU number
+
+Basic Usage Example
+-------------------
+
+To collect HTM trace data for a specific chip:
+
+.. code-block:: sh
+
+ # perf record -C 1 -e htm/nodalchipindex=2,nodeindex=0,htm_type=1/ <workload>
+
+In this example:
+
+- ``-C 1``: Collect on CPU 1
+- ``nodeindex=0``: Target node 0
+- ``nodalchipindex=2``: Target chip 2 within node 0
+- ``htm_type=1``: HTM trace type 1
+
+.. code-block:: sh
+
+ # perf record -m,256 -e htm/coreindexonchip=6,nodalchipindex=0,nodeindex=0,htm_type=2,cpu=16/ -a sleep 1
+
+In this example:
+
+- ``cpu=16``: Collect on CPU 16
+- ``nodeindex=0``: Target node 0
+- ``nodalchipindex=0``: Target chip 0 within node 0
+- ``coreindexonchip=6``: Target core 6
+- ``htm_type=2``: HTM trace type 2
+- ``-m,256``: specifies number of mmap pages
+
+Running trace collection for multiple targets:
+
+.. code-block:: sh
+
+ # perf record -m,256 -e htm/nodalchipindex=2,nodeindex=0,htm_type=1,cpu=8/ -e htm/nodalchipindex=1,nodeindex=0,htm_type=1,cpu=9/ -a sleep 1
+
+
+In this example, trace is collected for two events on different target chips
+
+Output Files
+------------
+
+``perf record`` produces ``perf.data``. Running ``perf report`` on that
+file invokes the HTM auxtrace handler, which writes the output files:
+
+- **htm.bin.nX.pX.cX** : raw bus-trace AUX data for node X, chip X, core X
+- **translation.nX.pX.cX** : memory-configuration records for the same target
+
+.. code-block:: sh
+
+ # perf report
+ # ls htm.bin.* translation.*
+ htm.bin.n0.p2.c0 translation.n0.p2.c0
+
+Note: ``perf report -D`` prints AUX buffer sizes but does not produce
+the output files. Use plain ``perf report`` to extract trace data.
+
+Decoding Output Files
+---------------------
+
+Pass the generated files to htmdecode for trace decoding::
+
+ htmdecode htm.bin.n0.p2.c0 translation.n0.p2.c0
+
+PMU Restrictions
+----------------
+
+The HTM PMU uses ``PERF_PMU_CAP_EXCLUSIVE``, which enforces a limit of one
+active event per target (node/chip/core tuple) at a time. Attempting to open
+a second event on the same target returns ``-EBUSY``.
+
+HTM events must be pinned to a CPU using the ``cpu=N`` parameter in the event
+config. Using ``-a`` (system-wide) without ``cpu=N`` causes ``-EBUSY`` from
+the kernel because the HTM PMU operates on physical hardware addresses and
+requires an explicit CPU binding.
+
+Complete Workflow Example
+-------------------------
+
+.. code-block:: sh
+
+ # Step 1: Collect trace data
+ perf record -m,256 -e htm/nodalchipindex=2,nodeindex=0,htm_type=1,cpu=9/ -a sleep 5
+
+ # Step 2: Extract trace and memory-config files
+ perf report
+
+ # Step 3: Verify output files
+ ls htm.bin.* # htm.bin.n0.p2.c0
+ ls translation.* # translation.n0.p2.c0
+ ls perf.data
+
+ # Step 4: Decode the trace
+ htmdecode htm.bin.n0.p2.c0 translation.n0.p2.c0
+
+Benefits of using HTM interface
=======================================
It is now possible to collect traces for a particular core/chip
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread