* [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support
@ 2026-08-07 14:37 Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 UTC (permalink / raw)
To: linuxppc-dev, maddy
Cc: linux-perf-users, atrajeev, hbathini, tejas05, venkat88, tshah,
usha.r2
Overview:
The Hardware Trace Macro (HTM) on POWER systems provides hardware-level
bus-trace data for a specific node/chip/core target within a logical
partition. It is accessed via the H_HTM hypervisor call and traces
continuously at node, chip, or core scope independent of task scheduling.
This series adds a new "htm" PMU driver under arch/powerpc/perf/ that
exposes HTM trace collection via the standard perf interface using the
perf AUX buffer infrastructure, together with ABI and RST documentation.
Patch overview:
Patch 1 introduces the core HTM PMU driver (htm-perf.c). The event
configuration encodes the trace target as a 28-bit value in
perf_event_attr.config:
bits 0-3: htm_type (HTM_CORE=2, HTM_NEST=1, HTM_LLAT=3)
bits 4-11: nodeindex
bits 12-19: nodalchipindex
bits 20-27: coreindexonchip
The PMU lifecycle maps to H_HTM_OP_CONFIGURE/DECONFIGURE at add/del time
and H_HTM_OP_START/STOP at start/stop time. Context-switch callbacks
(PERF_EF_RELOAD / PERF_EF_UPDATE) are intentionally ignored because HTM
traces the hardware scope, not individual tasks. Tracing state is kept
in 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.
Patch 2 adds duplicate-reservation protection. When 'perf record -a' is
used, perf opens system-wide events on all CPUs in parallel. Without
driver-side tracking, concurrent opens for the same HTM target (node,
chip, core, type) can race and issue duplicate H_HTM_OP_CONFIGURE flows.
A global reserved-targets list is added; htm_event_init() rejects any
open that matches an already-reserved tuple, while different targets can
still be opened simultaneously on different CPUs.
Patch 3 adds AUX ring-buffer support so that high-volume trace data can
be streamed into a perf AUX buffer. The driver opts 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.
A page-by-page physical-continuity scan is performed before each dump to
detect and bound any fragmentation gaps, preventing silent memory
corruption. event->count is set to the number of 128-byte HTM trace
records written on a successful dump, 1 when the AUX buffer is
temporarily full but the hypervisor stream is intact, and 0 when the
stream is exhausted or a hard error occurs; the perf tool uses this as
a drain signal to decide whether another read pass is needed before
closing the event.
Patch 4 adds system memory configuration capture. After writing trace
data to the AUX buffer, htm_event_read() calls H_HTM_OP_DUMP_SYSMEM_CONF
and emits the returned physical-to-logical address mapping records as
PERF_SAMPLE_RAW data. Keeping trace data in the AUX stream and memory
configuration in raw samples allows each to be decoded independently in
userspace.
Patch 5 adds ABI documentation for the sysfs entries under
/sys/bus/event_source/devices/htm/, covering the format attribute
(config bit layout) and the events attribute group.
Patch 6 extends Documentation/arch/powerpc/htm.rst with a new section
on the perf interface: event syntax, required options, the two output
files (htm.bin.nX.pX.cX.tX and translation.nX.pX.cX.tX), and how to pass
them to htmdecode for trace decoding.
Usage example:
Collect HTM trace data from two simultaneous chip targets:
# 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 10
# perf report
Output files written by perf report:
htm.bin.n0.p2.c0.t1 raw HTM bus-trace, chip 2
htm.bin.n0.p1.c0.t1 raw HTM bus-trace, chip 1
translation.n0.p2.c0.t1 memory config records, chip 2
translation.n0.p1.c0.t1 memory config records, chip 1
The htm.bin.* files can then be decoded with htmdecoder to produce
trace output.
Testing:
Tested on POWER11 with two simultaneous HTM targets. Both htm.bin.*
and translation.* files are produced correctly.
# perf record -C 9 -m,256 \
-e htm/nodalchipindex=2,nodeindex=0,htm_type=1/ -a sleep 3
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 256.277 MB perf.data ]
# perf record -m,512 \
-e htm/htm_type=3,nodalchipindex=1,nodeindex=0,coreindexonchip=6,cpu=8/ \
-e htm/htm_nest,nodalchipindex=1,nodeindex=0,cpu=16/ -a sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 514.095 MB perf.data ]
Athira Rajeev (6):
powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data
powerpc/perf: Reject duplicate HTM target reservations
powerpc/perf: Add AUX buffer management to capture HTM trace data
powerpc/perf: Capture the HTM memory configuration as part of perf
data
docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event
format entries for htm pmu
powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU
.../sysfs-bus-event_source-devices-htm | 36 +
Documentation/arch/powerpc/htm.rst | 158 ++-
arch/powerpc/perf/Makefile | 2 +-
arch/powerpc/perf/htm-perf.c | 1122 +++++++++++++++++
4 files changed, 1314 insertions(+), 4 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-event_source-devices-htm
create mode 100644 arch/powerpc/perf/htm-perf.c
--
2.53.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:58 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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, HTM_LLAT=3)
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 errors 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): on any non-success result from the
configure hcall, calls perf_event_disable_inatomic() and returns 0.
event_sched_in() in the perf core converts any non-zero pmu->add()
return to -EAGAIN and leaves the event as PERF_EVENT_STATE_INACTIVE,
which the mux would retry on every tick. Returning 0 and calling
perf_event_disable_inatomic() instead schedules a task-work callback
that sets PERF_EVENT_STATE_OFF — permanently disabled, never
rescheduled by the mux.
- 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 V5:
- Add HTM_LLAT (htm_type=3) as a third supported HTM type alongside
HTM_NEST and HTM_CORE. Add EVENT(HTM_LLAT, 0x3) and
GENERIC_EVENT_ATTR/PTR entries, and extend the htm_type switch in
htm_event_init() with case HTM_LLAT.
- htm_target_id: add int configured field to track whether
H_HTM_OP_CONFIGURE succeeded for this event. Set to 1 in
htm_event_add() after a successful CONFIGURE, cleared to 0 if the
subsequent H_HTM_OP_START fails. htm_event_del() guards its
H_HTM_OP_DECONFIGURE call with if (!target->configured) return; to
prevent issuing DECONFIGURE when CONFIGURE was never completed or was
already undone, which would destroy a concurrent trace session on the
same target.
- htm_event_init(): extend the sample_type rejection to cover
PERF_SAMPLE_CALLCHAIN, PERF_SAMPLE_STACK_USER, PERF_SAMPLE_REGS_USER,
and PERF_SAMPLE_AUX in addition to the existing PERF_SAMPLE_REGS_INTR
check. All of these either add variable-length data to the ring buffer
record (overflowing the fixed HTM_MEM_BUF_SIZE budget), require kernel
stack walking that is unsafe from the HTM read path, or conflict with
AUX buffer ownership. A single combined check replaces the two
separate guards.
Changes in V4:
- htm_event_add(): replace -ENODEV / -EIO returns on configure or start
failure with perf_event_disable_inatomic(event) + return 0.
event_sched_in() in the perf core overrides any non-zero pmu->add()
return to -EAGAIN and sets PERF_EVENT_STATE_INACTIVE, which the mux
retries on every tick; this would flood the hypervisor with
H_HTM_OP_CONFIGURE hcalls when it has requested a long backoff.
perf_event_disable_inatomic() schedules a task-work callback that sets
PERF_EVENT_STATE_OFF, permanently excluding the event from the mux.
- PERF_EF_RELOAD comment in htm_event_start(): expanded to explain that
HTM does not use frequency mode, so there is no period or counter to
unthrottle, and that the guard is belt-and-suspenders because
.task_ctx_nr = perf_invalid_context prevents pmu->start() from being
called on context-switch-in entirely.
- PERF_EF_UPDATE comment in htm_event_stop(): expanded to explain that
HTM has no counter to snapshot on context-switch-out (data flows into
the AUX buffer via pmu->read), and that the guard is belt-and-suspenders
for the same reason as above.
- Added explanation above HTM_TRACING_ACTIVE that pmu->add/del are never
called on context switch: .task_ctx_nr = perf_invalid_context forces
CPU-wide-only placement; perf_event_context_sched_out/in() only walks
task->perf_event_ctxp which is NULL for tasks with no task-context
events, so the switch path returns immediately without touching HTM.
- Fixed typos: "erros" -> "errors" (commit body and two in-code comments).
- htm_event_init(): reject attr.freq with -EINVAL. HTM is a
hardware-scope bus tracer with no counter or sample period. Frequency
mode makes the core call pmu->stop/start every tick to adjust the
sample period — meaningless for HTM — and would corrupt event->count
semantics (the driver uses it as a record count; the freq machinery
would interpret it as a sample-rate measurement). perf record already
forces attr.freq=0 in htm_recording_options(), but rejecting it in the
kernel closes the gap for any direct perf_event_open() caller.
- htm_event_init(): reject PERF_SAMPLE_REGS_INTR with -EOPNOTSUPP.
The HTM_MEM_BUF_SIZE record size calculation (patch 4) assumes a fixed
92-byte overhead; PERF_SAMPLE_REGS_INTR adds sizeof(struct pt_regs)
(~296 bytes on PowerPC) to each sample, overflowing the __u16
perf_event_header.size limit and corrupting the ring buffer.
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).
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 | 517 +++++++++++++++++++++++++++++++++++
2 files changed, 518 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..cbe4f62813ee
--- /dev/null
+++ b/arch/powerpc/perf/htm-perf.c
@@ -0,0 +1,517 @@
+// 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_NEST, 0x1);
+EVENT(HTM_CORE, 0x2);
+EVENT(HTM_LLAT, 0x3);
+
+GENERIC_EVENT_ATTR(htm_nest, HTM_NEST);
+GENERIC_EVENT_ATTR(htm_core, HTM_CORE);
+GENERIC_EVENT_ATTR(htm_llat, HTM_LLAT);
+
+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),
+ GENERIC_EVENT_PTR(HTM_LLAT),
+ 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 */
+ int configured; /* 1 after H_HTM_OP_CONFIGURE succeeds; 0 otherwise */
+};
+
+/* 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.
+ *
+ * Note: pmu->add() and pmu->del() are NOT called on every context switch
+ * for HTM events. .task_ctx_nr = perf_invalid_context means HTM events
+ * can only be opened CPU-wide (perf_event_open() returns -EINVAL for any
+ * task-specific open). CPU-wide events live in cpuctx->ctx, not in
+ * task->perf_event_ctxp. perf_event_context_sched_out/in() only walks
+ * task->perf_event_ctxp and returns immediately when it is NULL, so
+ * pmu->add/del are never triggered by a context switch. The
+ * PERF_EF_RELOAD / PERF_EF_UPDATE guards below checks this.
+ */
+#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;
+
+ /*
+ * Reject sample types whose payload size cannot be statically bounded
+ * or whose maximum size would overflow the 16-bit
+ * perf_event_header.size limit when combined with HTM_MEM_BUF_SIZE:
+ *
+ * PERF_SAMPLE_CALLCHAIN — (1 + nr) * 8 bytes; nr is runtime-defined,
+ * default up to 127, configurable to 1024+.
+ * PERF_SAMPLE_STACK_USER — up to ~65443 bytes depending on header size.
+ * PERF_SAMPLE_REGS_USER — depends on sample_regs_user bitmask at open
+ * time; up to 44 regs × 8 = 360 bytes.
+ * PERF_SAMPLE_REGS_INTR — same as REGS_USER.
+ * PERF_SAMPLE_AUX — in-sample AUX snapshot; size is runtime.
+ *
+ * All remaining sample types (IP, TID, TIME, ADDR, PERIOD, CPU, etc.)
+ * contribute exactly one u64 each. Their worst-case combined overhead
+ * is accounted for in HTM_MEM_BUF_SIZE (patch 4): the buffer is sized
+ * so that HTM_MEM_BUF_SIZE + max fixed overhead <= 65535.
+ */
+ if (event->attr.sample_type & (PERF_SAMPLE_CALLCHAIN |
+ PERF_SAMPLE_STACK_USER |
+ PERF_SAMPLE_REGS_USER |
+ PERF_SAMPLE_REGS_INTR |
+ PERF_SAMPLE_AUX))
+ return -EOPNOTSUPP;
+
+ /*
+ * HTM is a continuous bus tracer with no counter or sample period.
+ * Frequency mode makes the core call pmu->stop/start every tick to
+ * adjust the sample period — meaningless for HTM — and would corrupt
+ * event->count semantics (the driver uses it as a record count).
+ * perf record already forces attr.freq=0 in htm_recording_options(),
+ * but reject it here to close the gap for direct perf_event_open() callers.
+ */
+ if (event->attr.freq)
+ return -EINVAL;
+
+ parse_htm_config(config, &cfg);
+ switch (cfg.htmtype) {
+ case HTM_CORE:
+ case HTM_NEST:
+ case HTM_LLAT:
+ 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 (PERF_EF_RELOAD). HTM is a
+ * not a frequency-mode counter PMU. PERF_EF_RELOAD is used by
+ * the core to restart a counter after unthrottling a frequency-based
+ * event; HTM has no period or frequency knob and nothing to unthrottle.
+ * In practice this path is never reached because
+ * .task_ctx_nr = perf_invalid_context prevents task-context placement,
+ * so pmu->start() is never called on context-switch-in.
+ * The guard is kept as check.
+ */
+ 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 (PERF_EF_UPDATE).
+ * HTM is a continuous bus tracer; stopping the hardware on
+ * every context switch would break continuous tracing, which is the
+ * entire purpose of this PMU. PERF_EF_UPDATE is used by the core to
+ * snapshot a counter value on context-switch-out; HTM has no counter
+ * to read (data flows into the AUX buffer via pmu->read). In
+ * practice this path is never reached because .task_ctx_nr =
+ * perf_invalid_context prevents task-context placement, so
+ * pmu->stop() is never called on context-switch-out. The guard is
+ * kept as check.
+ */
+ 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) or any other error,
+ * the loop exits and we call perf_event_disable_inatomic() + return 0.
+ * Returning any non-zero value from pmu->add() would cause
+ * event_sched_in() to convert it to -EAGAIN and leave the event as
+ * PERF_EVENT_STATE_INACTIVE, which the mux retries on every tick,
+ * flooding the hypervisor with H_HTM_OP_CONFIGURE hcalls exactly when
+ * it has requested a long backoff delay. perf_event_disable_inatomic()
+ * schedules a task-work callback that sets PERF_EVENT_STATE_OFF,
+ * permanently excluding the event from the mux.
+ */
+ 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) {
+ perf_event_disable_inatomic(event);
+ return 0;
+ }
+
+ /*
+ * 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.
+ * Mark configured so htm_event_del() knows a matching DECONFIGURE is
+ * required. This flag is the only gate; htm_event_del() must not
+ * call H_HTM_OP_DECONFIGURE unless this driver issued the paired
+ * H_HTM_OP_CONFIGURE — doing so would silently destroy a concurrent
+ * trace session that owns the same hardware target.
+ */
+ target->configured = 1;
+ 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 another error is returned here, the
+ * resource cannot be reclaimed in this context; log the
+ * failure so it is visible in the kernel log.
+ * Call perf_event_disable_inatomic() so the event is
+ * permanently disabled (PERF_EVENT_STATE_OFF) rather
+ * than left inactive and retried by the mux.
+ */
+ 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);
+ }
+ /*
+ * We already issued DECONFIGURE above; clear configured so
+ * htm_event_del() does not issue a second one.
+ */
+ target->configured = 0;
+ perf_event_disable_inatomic(event);
+ return 0;
+ }
+ }
+
+ 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 errors 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);
+
+ /*
+ * Only issue H_HTM_OP_DECONFIGURE if this driver successfully issued
+ * the paired H_HTM_OP_CONFIGURE. If htm_event_add() failed before or
+ * during CONFIGURE (configured == 0), there is nothing to tear down.
+ * Issuing DECONFIGURE without a prior CONFIGURE would silently destroy
+ * a concurrent trace session that owns the same hardware target and
+ * produce spurious pr_err() noise for an expected H_STATE / H_NOT_AVAILABLE
+ * response.
+ */
+ if (!target->configured)
+ return;
+
+ /*
+ * 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.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH V5 2/6] powerpc/perf: Reject duplicate HTM target reservations
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
` (3 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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 V5: - No changes from V4.
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 cbe4f62813ee..c1ce12605014 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -80,10 +80,14 @@ 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 */
int configured; /* 1 after H_HTM_OP_CONFIGURE succeeds; 0 otherwise */
+ struct list_head list;
};
/* Helper to parse the 28-bit event config into distinct fields */
@@ -168,7 +172,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;
}
@@ -176,6 +190,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;
@@ -239,11 +254,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.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:57 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
` (2 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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.
perf_output_handle objects are declared on the stack in every function
that calls perf_aux_output_begin() (aux_buf_reset_for_start() and
htm_dump_sample_data()). A per-CPU handle embedded in a static variable
would be shared between an outer AUX transaction and a nested call from
NMI context on the same CPU: perf_aux_output_begin()'s error path writes
handle->event = NULL on the handle it receives; if both the outer and
nested caller share the same object, this clears the outer transaction's
event pointer and causes perf_aux_output_end() to return early without
decrementing rb->aux_nest, permanently stalling the ring buffer. Using
stack-local handles eliminates the aliasing entirely.
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.
One-shot dump model:
HTM hardware fills a fixed physical memory buffer (configured by
H_HTM_OP_CONFIGURE) while tracing is active. When pmu->read is
first called, htm_dump_sample_data() issues H_HTM_OP_STOP to freeze
that buffer, then copies its contents into the perf AUX ring buffer
one chunk at a time across successive pmu->read calls (each call
advances aux_buf->head by chunk_size and returns; the perf drain loop
calls pmu->read again until event->count reaches zero).
The hardware is NOT restarted between pmu->read calls, and is NOT
restarted after the dump finishes. This is intentional:
- H_HTM_OP_DUMP_DATA reads the same frozen hardware buffer on every
call, addressed by the advancing aux_buf->head offset. Restarting
the hardware mid-drain would cause it to overwrite the buffer
content that has not been transferred yet, corrupting all
subsequent chunks.
- The intended usage is a bounded recording session (e.g.
perf record -C 8 -m,512 -e htm/.../ sleep 1): hardware traces for
the duration of the sleep, then the perf tool drains the full
buffer in one pass at event close. A new perf record invocation
opens a fresh event, which issues H_HTM_OP_START again.
If H_HTM_OP_STOP fails on the first pmu->read call, the dump is
aborted and event->count is set to 0. The hardware buffer is still
running; it will be stopped by H_HTM_OP_DECONFIGURE in htm_event_del()
when the event is closed, preventing a resource leak.
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.
Hardware buffer boundary check:
H_HTM_OP_STATUS returns the currently allocated HTM buffer size for
the target. The status output buffer header byte 0x01 holds
CurrentNestHtmBufferSizeInPowerOf2 (HTM_NEST) or
CurrentCoreHtmBufferSizeInPowerOf2 (HTM_CORE); both types use the
same offset and length (offset 0x01, 1 byte). The actual buffer
size is 1 << byte[0x01].
htm_event_add() issues H_HTM_OP_STATUS after a successful
H_HTM_OP_CONFIGURE, allocating a 32-byte scratch buffer (enough for
the header), reads byte 0x01, computes hw_buf_size = 1ULL << val,
stores it in target->hw_buf_size, then frees the scratch buffer.
If the hcall fails, hw_buf_size remains 0.
In htm_dump_sample_data(), after the contiguous-window clamp and
before the H_HTM_OP_DUMP_DATA call, hw_buf_size is used as a hard
ceiling: if aux_buf->head has reached hw_buf_size the hardware buffer
is fully consumed — collect_htm_trace is cleared and the dump returns
0 immediately without issuing any further hcalls. chunk_size is also
clamped to hw_buf_size - aux_buf->head so the final chunk never
overshoots the hardware boundary. When hw_buf_size == 0 (status
hcall failed) the check is skipped and the existing contiguous-window
clamp and hcall EOF signal remain in effect.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V5:
- Use stack-local struct perf_output_handle in aux_buf_reset_for_start()
and htm_dump_sample_data() instead of a per-CPU embedded handle. A
shared per-CPU handle is corrupted when perf_aux_output_begin()'s error
path writes handle->event = NULL on a nested (NMI) call to the same
object, clearing the outer transaction's event pointer and permanently
stalling rb->aux_nest. Stack-local handles eliminate the aliasing.
As a consequence, struct htm_pmu_ctx (which contained only the handle)
and its DEFINE_PER_CPU are removed; they are no longer needed.
- Add u64 hw_buf_size to struct htm_target_id. Populated in
htm_event_init() by a GFP_KERNEL H_HTM_OP_STATUS hcall using a
PAGE_SIZE-aligned buffer (the hypervisor requires page alignment):
status_buf[0x01] gives the current buffer size as a power-of-two
exponent, so hw_buf_size = 1ULL << status_buf[0x01]. The hcall is
issued in htm_event_init() (process context, GFP_KERNEL) rather than
htm_event_add() (atomic, GFP_ATOMIC) to avoid unreliable PAGE_SIZE
allocations in atomic context; H_HTM_OP_STATUS is independent of
CONFIGURE and returns valid data before any session is opened.
htm_dump_sample_data() uses hw_buf_size to clamp chunk_size and to
detect end-of-data (aux_buf->head >= hw_buf_size) without a failed
hcall. On boundary hit, pivot to htm_collect_memory_config() via
goto out rather than returning 0, so the memory-config drain is not
skipped.
- Add in_nmi() guard at the top of htm_event_read(). The function
issues hypervisor calls and mutates target->tracing_active and
aux_buf->head, none of which are NMI-safe. Returning early from NMI
context preserves the existing event->count so the drain loop retries
on the next scheduled pmu->read() call.
Changes in V4:
- htm_event_start(): on a genuine start (not PERF_EF_RELOAD), open a brief
perf_aux_output_begin()/perf_aux_output_end(0) transaction to reset
collect_htm_trace=1 and head=0 on the AUX buffer. Without this,
collect_htm_trace cleared by a prior hard hcall error would permanently
prevent trace data from being written for all subsequent sessions.
- htm_setup_aux(): reject snapshot (overwrite) mode by returning NULL when
snapshot is true. In overwrite mode perf_aux_output_begin() leaves
handle->size as zero, which causes htm_dump_sample_data() to return ENOSPC
on every call and htm_event_read() to set event->count=1, creating an
infinite drain loop in userspace. Returning NULL causes perf_event_open()
to return EINVAL to the caller.
- Commit body: clarified the reentrancy description to make explicit that
on a nested perf_aux_output_begin() call (e.g. from NMI context), it is
the *nested* handle->event that is set to NULL in the err path, not the
outer handle. The outer handle->event remains valid throughout the outer
perf_aux_output_end() call.
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 | 371 ++++++++++++++++++++++++++++++++++-
1 file changed, 370 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index c1ce12605014..f72ee661c084 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -88,6 +88,7 @@ struct htm_target_id {
int tracing_active; /* HTM_TRACING_ACTIVE / HTM_TRACING_INACTIVE */
int configured; /* 1 after H_HTM_OP_CONFIGURE succeeds; 0 otherwise */
struct list_head list;
+ u64 hw_buf_size; /* hardware buffer size from H_HTM_OP_STATUS (1<<byte[0x01]), 0 if unknown */
};
/* Helper to parse the 28-bit event config into distinct fields */
@@ -99,6 +100,38 @@ 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;
+};
+
+/*
+ * Reset AUX buffer state at the start of a new trace session.
+ * collect_htm_trace is cleared on any hard hcall error; without
+ * this reset, restarting the event via ioctl(ENABLE) after such an
+ * error would permanently drop all future trace data.
+ *
+ * Uses a stack-local handle to avoid aliasing with an in-progress
+ * AUX transaction on the same CPU (e.g. from NMI context).
+ */
+static void aux_buf_reset_for_start(struct perf_event *event)
+{
+ struct perf_output_handle handle;
+ struct htm_pmu_buf *aux_buf;
+
+ aux_buf = perf_aux_output_begin(&handle, event);
+ if (aux_buf) {
+ aux_buf->collect_htm_trace = 1;
+ aux_buf->head = 0;
+ perf_aux_output_end(&handle, 0);
+ }
+}
+
/*
* Check the return code for H_HTM hcall.
* Return 1 if either H_PARTIAL or H_SUCCESS is returned.
@@ -191,6 +224,10 @@ static int htm_event_init(struct perf_event *event)
u64 config = event->attr.config;
struct htm_config cfg;
struct htm_target_id *target, *tmp;
+ u8 *status_buf;
+ long src;
+ ssize_t sret;
+ int sretries = 0;
if (event->attr.inherit)
return -EOPNOTSUPP;
@@ -277,6 +314,32 @@ static int htm_event_init(struct perf_event *event)
list_add_tail(&target->list, &htm_active_targets_list);
mutex_unlock(&htm_targets_lock);
+ /*
+ * Query the hardware-allocated HTM buffer size via H_HTM_OP_STATUS.
+ * The status output buffer header byte 0x01 holds
+ * CurrentNestHtmBufferSizeInPowerOf2 (for HTM_NEST) or
+ * CurrentCoreHtmBufferSizeInPowerOf2 (for HTM_CORE); both types
+ * use the same offset (0x01) and length (1 byte).
+ * hw_buf_size is used in htm_dump_sample_data() to bound dump
+ * offsets, preventing H_HTM_OP_DUMP_DATA calls past the end of the
+ * hardware buffer. On failure hw_buf_size stays 0 and the boundary
+ * check is skipped gracefully — the contiguous-window clamp still
+ * applies.
+ */
+ status_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (status_buf) {
+ do {
+ src = htm_hcall_wrapper(htmflags, cfg.nodeindex,
+ cfg.nodalchipindex, cfg.coreindexonchip,
+ cfg.htmtype, H_HTM_OP_STATUS,
+ virt_to_phys(status_buf), PAGE_SIZE, 0);
+ sret = htm_return_check(src);
+ } while (sret == -EBUSY && ++sretries < MAX_RETRIES);
+ if (sret > 0)
+ target->hw_buf_size = 1ULL << status_buf[0x01];
+ kfree(status_buf);
+ }
+
event->pmu_private = target;
event->destroy = reset_htm_active;
return 0;
@@ -318,6 +381,9 @@ static void htm_event_start(struct perf_event *event, int flags)
if (ret > 0) {
target->tracing_active = HTM_TRACING_ACTIVE;
event->hw.state &= ~PERF_HES_STOPPED;
+
+ if (!(flags & PERF_EF_RELOAD))
+ aux_buf_reset_for_start(event);
}
}
@@ -510,8 +576,308 @@ 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 perf_output_handle handle;
+ 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; use a stack-local handle to prevent
+ * NMI reentrancy from corrupting an outer transaction's handle.
+ */
+ aux_buf = perf_aux_output_begin(&handle, event);
+ if (!aux_buf)
+ return 0;
+
+ if (!aux_buf->collect_htm_trace) {
+ /*
+ * collect_htm_trace is cleared on hcall error and reset to 1
+ * in htm_event_start() when tracing restarts. If it is still
+ * 0 here the event was disabled due to a prior hard error;
+ * end the AUX transaction and return 0 so the drain loop stops.
+ */
+ perf_aux_output_end(&handle, 0);
+ return 0;
+ }
+
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ /*
+ * First pmu->read call of the drain sequence: stop the hardware
+ * to freeze the HTM buffer before reading it. This is a
+ * one-shot dump — the buffer is static for the entire drain;
+ * the hardware is not restarted between chunks or after the
+ * dump completes. Restarting mid-drain would overwrite buffer
+ * content that has not yet been transferred to the AUX ring.
+ * A new perf record session issues H_HTM_OP_START afresh.
+ */
+ htm_event_stop(event, 0);
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ /*
+ * H_HTM_OP_STOP failed — cannot dump a live buffer.
+ * Abort the dump; event->count = 0 stops the drain loop.
+ * The hardware is still running; htm_event_del() will
+ * call H_HTM_OP_DECONFIGURE which implicitly stops it,
+ * preventing a resource leak.
+ */
+ perf_aux_output_end(&handle, 0);
+ return -EIO;
+ }
+ }
+
+ /* Derive the exact target destination point directly out of active ring pointers */
+ dump_offset = 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.
+ * handle.size is always page-aligned: perf_aux_output_begin() computes
+ * it as the distance from the write pointer to the wakeup boundary,
+ * rounded to PAGE_SIZE. Masking with PAGE_MASK is therefore a no-op
+ * but is kept to make the page-granularity contract explicit.
+ */
+ chunk_size = 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;
+
+ /*
+ * Bound the dump to the hardware-allocated buffer size reported by
+ * H_HTM_OP_STATUS (stored in target->hw_buf_size at configure time).
+ * aux_buf->head is the byte offset of the next chunk to dump; once
+ * it reaches hw_buf_size the hardware buffer is exhausted and no
+ * further H_HTM_OP_DUMP_DATA calls should be issued.
+ * hw_buf_size == 0 means the status hcall failed at configure time;
+ * skip the check and let the hcall itself signal end-of-data.
+ */
+ if (target->hw_buf_size) {
+ if (aux_buf->head >= target->hw_buf_size) {
+ aux_buf->collect_htm_trace = 0;
+ perf_aux_output_end(&handle, 0);
+ return 0;
+ }
+ if (chunk_size > target->hw_buf_size - aux_buf->head)
+ chunk_size = target->hw_buf_size - aux_buf->head;
+ }
+
+ 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(&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(&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(&handle, 0);
+ return ret;
+}
+
static void htm_event_read(struct perf_event *event)
{
+ ssize_t ret;
+
+ /*
+ * Refuse to run from NMI context. htm_dump_sample_data() issues
+ * hypervisor calls, mutates target->tracing_active, and writes
+ * aux_buf->head — none of which are NMI-safe.
+ *
+ * perf_event_read_local() (used by BPF helpers such as
+ * bpf_perf_event_read_value()) does not check PERF_PMU_CAP_NO_NMI,
+ * so an explicit in_nmi() guard here is required.
+ *
+ * Returning without touching event->count preserves whatever value
+ * was set by the previous pmu->read() call. If the drain is still
+ * in progress, event->count remains non-zero and the drain loop
+ * keeps retrying. The next scheduled pmu->read() — which will not
+ * be in NMI context — will proceed normally and complete the dump.
+ * This mirrors the stack-local handle fix: both let the NMI path
+ * return without mutating any shared state.
+ */
+ if (in_nmi())
+ return;
+
+ 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;
+
+ /*
+ * Snapshot (overwrite) mode is not supported. In overwrite mode
+ * perf_aux_output_begin() leaves handle->size = 0, which would
+ * cause htm_dump_sample_data() to return ENOSPC on every call,
+ * setting event->count = 1 and creating an infinite drain loop
+ * in userspace. Reject it here so perf_event_open() returns
+ * EINVAL to the caller.
+ */
+ if (snapshot)
+ 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 = {
@@ -524,7 +890,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.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (2 preceding siblings ...)
2026-08-07 14:37 ` [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:51 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
5 siblings, 1 reply; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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 and iterator state. Once AUX trace dumping completes,
htm_event_read() calls htm_collect_memory_config() which issues one
H_HTM_OP_DUMP_SYSMEM_CONF hcall and emits the result as a single
PERF_SAMPLE_RAW record, then returns immediately. The perf drain loop
calls htm_event_read() repeatedly until event->count reaches zero,
giving userspace a chance to drain the ring buffer between every record.
This mirrors the AUX trace path design and avoids the ring-buffer-overflow
problem that a while(true) loop would create.
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),
overflow_handler has already run unconditionally (writing the sample to
the ring buffer) before the non-zero return. mem_start is therefore
advanced so the same block is not emitted again, and -ENOSPC is returned
so event->count is set to 1 and the drain loop keeps retrying to emit
the next block once the event is unthrottled.
Keep HTM tracing state in event->pmu_private via htm_target_id. AUX
private state is used only for dump progress and staging buffers.
Concurrency and locking:
target->tracing_active and target->configured:
Every read and write of these fields occurs in pmu->add(),
pmu->del(), pmu->start(), pmu->stop(), and pmu->read(). All of
these callbacks are invoked by the perf core under ctx->lock with
IRQs disabled (event_sched_in/out, __perf_event_read). IRQs
disabled means regular interrupts cannot preempt these paths. NMI
can still fire — but see below.
aux_buf->head and aux_buf->collect_htm_trace:
These fields are only written from htm_dump_sample_data() (called
from pmu->read()), which is also under ctx->lock with IRQs disabled.
Additionally, perf_aux_output_begin() increments rb->aux_nest on
entry and perf_aux_output_end() decrements it on exit. If an NMI
fires while an AUX transaction is open (aux_nest > 0), any nested
perf_aux_output_begin() call hits the WARN_ON_ONCE(nest) guard and
returns NULL immediately — the NMI path exits without touching
aux_buf->head or any other shared state.
BPF NMI path (perf_event_read_local):
The only kernel path that calls pmu->read() without holding
ctx->lock is perf_event_read_local(), used by BPF helpers such as
bpf_perf_event_read_value(). perf_event_read_local() does not
check PERF_PMU_CAP_NO_NMI, so it can invoke pmu->read() from NMI
context.
htm_event_read() guards against this with an explicit in_nmi()
check at entry: if called from NMI, it returns immediately without
touching event->count, target->tracing_active, aux_buf->head, or
issuing any hcall. event->count retains its previous value, so
if the drain is in progress the drain loop remains alive and the
next scheduled pmu->read() (not in NMI context) completes the dump.
Additionally, htm_event_init() (patch 2) prevents a second
perf_event_open() for the same target (returns -EBUSY), so a BPF
session and a perf session cannot hold the same target concurrently.
The in_nmi() guard is defence-in-depth against any future path
that bypasses this reservation.
No additional locking is required beyond ctx->lock (for target
fields) and rb->aux_nest (for AUX buffer fields).
Ring buffer full and silent drop of memory configuration records:
htm_collect_memory_config() uses perf_event_overflow() to emit records
to the main ring buffer. perf_event_overflow() returns non-zero only
when the event is throttled; it returns 0 both when the sample is
written successfully and when perf_output_begin() fails because the
ring buffer is full (in the latter case the sample is silently dropped
inside perf_output_sample()). In both 0-return cases aux_buf->mem_start
is advanced. This is the same behaviour as tracepoints and BPF perf
event helpers: when the ring buffer is full, records are dropped and the
iterator advances. The alternative — stalling the iterator on drop —
would loop forever whenever the ring buffer stayed full, blocking the
drain loop. Dropped records are visible to userspace through the
PERF_RECORD_LOST counter in the ring buffer header.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V5:
- htm_collect_memory_config(): zero struct pt_regs regs with memset()
before calling perf_fetch_caller_regs(). perf_fetch_caller_regs()
only initialises nip, msr, and gpr[1]; the remaining fields are left
as uninitialised stack bytes. Defensive zeroing prevents potential
kernel stack leakage and matches the pattern in trace_event_perf.c
and bpf_trace.c.
- htm_dump_sample_data(): handle perf_aux_output_begin() returning NULL
while memory-configuration collection is still in progress. When the
AUX ring buffer is full, perf_aux_output_begin() returns NULL and the
function previously returned 0, signalling EOF to htm_event_read() and
permanently abandoning the mem-config drain. The fix retrieves
aux_buf via perf_get_aux() and, if collect_htm_trace is already clear
but collect_htm_mem is still set, calls htm_collect_memory_config()
directly, keeping the drain alive independently of AUX ring pressure.
Changes in V4:
- htm_collect_memory_config(): restructured from a while(true) loop to a
single-hcall-per-call design, mirroring the AUX trace path. The old
loop had no way to break out when the ring buffer filled mid-loop:
perf_event_overflow() returns 0 for both "written" and "ring buffer
full / dropped", so iteration would continue, dropping every subsequent
record with no chance for userspace to drain the buffer. With one hcall
per call, the drain loop (arch_perf_record__need_read) calls
htm_event_read() → htm_collect_memory_config() once per pass, userspace
drains the ring buffer between passes, and ring buffer pressure is
handled naturally.
- htm_collect_memory_config(): on non-zero return from perf_event_overflow()
(event throttled), advance mem_start before returning -ENOSPC.
overflow_handler runs unconditionally inside __perf_event_overflow()
before the non-zero return, so the sample WAS written to the ring buffer.
The old code did not advance mem_start on throttle, causing the same
block to be emitted again on the next call — a duplicate sample bug.
- htm_setup_aux() / htm_free_aux(): removed emit_buf. emit_buf was a
second HTM_MEM_BUF_SIZE allocation used to keep raw.frag.data stable
across loop iterations (htm_mem_buf was overwritten by the next hcall).
With no loop, htm_mem_buf is not reused within a single call, so it
is stable throughout perf_event_overflow() and no memcpy or second
buffer is needed.
- htm_setup_aux(): changed kmalloc_node to kzalloc_node for htm_mem_buf.
The buffer is passed to the hypervisor and subsequently read by the perf
core; kzalloc_node ensures uninitialized heap bytes are never exposed if
the hypervisor leaves reserved fields or padding untouched.
- HTM_MEM_BUF_SIZE / PERF_SAMPLE_REGS_INTR: no change needed here.
PERF_SAMPLE_REGS_INTR is now rejected in htm_event_init() (patch 1),
so the 92-byte fixed overhead used in the HTM_MEM_BUF_SIZE calculation
is accurate for all sample types this driver accepts.
- struct pt_regs initialisation: no change needed. perf_fetch_caller_regs()
was already added in V3 (see "Changes in V3" below). The reviewer was
looking at V2 code.
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: added a comment
distinguishing the throttle path (collect_htm_mem left set, -ENOSPC
returned) 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 and iterator
state are introduced to manage the SYSMEM_CONF drain.
- 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 | 210 ++++++++++++++++++++++++++++++++++-
1 file changed, 206 insertions(+), 4 deletions(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index f72ee661c084..ec1ce1457764 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -108,6 +108,9 @@ struct htm_pmu_buf {
u64 head;
u64 size;
int collect_htm_trace;
+ void *htm_mem_buf; /* Staging area for H_HTM_OP_DUMP_SYSMEM_CONF hcall */
+ u64 mem_start; /* Hypervisor iterator position for DUMP_SYSMEM_CONF */
+ int collect_htm_mem; /* State flag tracking whether memory logging is ongoing */
};
/*
@@ -203,6 +206,168 @@ 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.
+ *
+ * HTM_MEM_BUF_SIZE is chosen to satisfy two constraints:
+ *
+ * 1. 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
+ *
+ * 2. 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
+ * constraints:
+ * - 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 - issue one H_HTM_OP_DUMP_SYSMEM_CONF hcall
+ * and emit the result as a single PERF_SAMPLE_RAW record.
+ *
+ * Mirrors the AUX trace path: one hcall per call, return immediately.
+ * htm_dump_sample_data() calls this once per htm_event_read() invocation;
+ * the perf drain loop calls htm_event_read() repeatedly until
+ * event->count reaches zero, giving userspace a chance to drain the ring
+ * buffer between every record. This avoids the ring-buffer-overflow
+ * problem that a while(true) loop would create: if the ring buffer fills
+ * mid-loop there is no way to break out and let userspace drain it.
+ *
+ * Returns the number of 32-byte memory configuration entries emitted
+ * (to_copy / 32) on success, -ENOSPC if throttled (sample was written,
+ * event temporarily paused — advance mem_start, retry next block next
+ * call), 0 if the stream ended normally, or a negative error code on
+ * hard failure. The caller uses the return value directly as
+ * event->count, consistent with the AUX trace path returning count..
+ */
+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;
+ long rc;
+ ssize_t ret;
+ int retries = 0;
+
+ /*
+ * Zero the full pt_regs before fetching the caller context.
+ * perf_fetch_caller_regs() on PowerPC only initialises nip, msr,
+ * gpr[1], and result; all other fields (link, ctr, xer, remaining
+ * GPRs) would otherwise contain uninitialized stack bytes. If the
+ * event is opened with PERF_SAMPLE_CALLCHAIN, perf_callchain_kernel()
+ * reads regs->link and writes it to the ring buffer, leaking kernel
+ * stack memory to userspace. PERF_SAMPLE_CALLCHAIN is rejected in
+ * htm_event_init(), but zeroing here is the safe defensive practice
+ * used by tracepoints and BPF perf-event helpers.
+ */
+ memset(®s, 0, sizeof(regs));
+ perf_fetch_caller_regs(®s);
+
+ /* Issue one hcall with the current iterator position */
+ 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.
+ * ret < 0 (error): hard failure.
+ * Both cases: clear collect_htm_mem so the next htm_event_read()
+ * call does not re-enter, and return so event->count is set to 0.
+ */
+ if (ret <= 0) {
+ aux_buf->collect_htm_mem = 0;
+ return ret;
+ }
+
+ /*
+ * Read next iterator value and payload size from the hcall response.
+ * next_start == 0 means this is the last batch.
+ */
+ next_start = be64_to_cpu(*((__be64 *)(htm_mem_buf + 0x8)));
+ 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)) {
+ aux_buf->collect_htm_mem = 0;
+ return -EIO;
+ }
+
+ /*
+ * htm_mem_buf is stable for the duration of this single call —
+ * no loop reuse, so raw.frag.data remains valid throughout
+ * perf_event_overflow(). No memcpy to a separate emit_buf needed.
+ */
+ perf_sample_data_init(&data, 0, event->hw.last_period);
+ memset(&raw, 0, sizeof(raw));
+ raw.frag.data = htm_mem_buf;
+ raw.frag.size = to_copy;
+ perf_sample_save_raw_data(&data, event, &raw);
+
+ if (perf_event_overflow(event, &data, ®s)) {
+ /*
+ * Event throttled: overflow_handler ran unconditionally before
+ * returning, so the sample WAS written to the ring buffer.
+ * Advance mem_start so the same block is not emitted again.
+ * Return -ENOSPC so htm_event_read() sets event->count=1,
+ * keeping the drain loop alive to emit the next block once
+ * the event is unthrottled.
+ */
+ aux_buf->mem_start = next_start;
+ if (!next_start)
+ aux_buf->collect_htm_mem = 0;
+ return -ENOSPC;
+ }
+
+ /*
+ * perf_event_overflow() returns 0 for both "sample written" and
+ * "ring buffer full, sample dropped" (perf_output_begin() failure
+ * inside perf_output_sample() is silent). Advance the iterator in
+ * both cases. This matches tracepoint / BPF perf-event helper
+ * behaviour: when the ring buffer is full, records are dropped and
+ * the stream continues. Stalling the iterator on drop would loop
+ * forever if the ring stayed full. Dropped records are counted in
+ * the PERF_RECORD_LOST entry in the ring buffer header.
+ */
+ aux_buf->mem_start = next_start;
+ if (!next_start)
+ aux_buf->collect_htm_mem = 0;
+
+ /*
+ * Return the number of 32-byte entries emitted. Dividing here keeps
+ * htm_event_read() free of format knowledge, consistent with the AUX
+ * trace path returning chunk_size / 128.
+ */
+ return (ssize_t)(to_copy / 32);
+}
+
static void reset_htm_active(struct perf_event *event)
{
struct htm_target_id *target = event->pmu_private;
@@ -594,10 +759,25 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
* NMI reentrancy from corrupting an outer transaction's handle.
*/
aux_buf = perf_aux_output_begin(&handle, event);
- if (!aux_buf)
+ if (!aux_buf) {
+ /*
+ * AUX ring buffer is full: perf_aux_output_begin() returned NULL.
+ * If the AUX trace dump is already complete but memory
+ * configuration collection is still in progress, we must not
+ * return 0 here — that would signal EOF to htm_event_read() and
+ * permanently abandon the mem config drain. Memory config
+ * records go to the main ring buffer via perf_event_overflow(),
+ * which is entirely independent of the AUX ring. Retrieve the
+ * aux_buf from the ring's aux_private and call directly.
+ */
+ struct htm_pmu_buf *fb = perf_get_aux(&handle);
+
+ if (fb && !fb->collect_htm_trace && fb->collect_htm_mem)
+ return htm_collect_memory_config(event, fb);
return 0;
+ }
- if (!aux_buf->collect_htm_trace) {
+ if (!aux_buf->collect_htm_trace && !aux_buf->collect_htm_mem) {
/*
* collect_htm_trace is cleared on hcall error and reset to 1
* in htm_event_start() when tracing restarts. If it is still
@@ -632,6 +812,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 = handle.head & (aux_buf->size - 1);
page_index = dump_offset >> PAGE_SHIFT;
@@ -701,8 +886,8 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
if (target->hw_buf_size) {
if (aux_buf->head >= target->hw_buf_size) {
aux_buf->collect_htm_trace = 0;
- perf_aux_output_end(&handle, 0);
- return 0;
+ ret = htm_collect_memory_config(event, aux_buf);
+ goto out;
}
if (chunk_size > target->hw_buf_size - aux_buf->head)
chunk_size = target->hw_buf_size - aux_buf->head;
@@ -757,6 +942,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(&handle, 0);
return ret;
}
@@ -862,7 +1049,21 @@ 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; allocate exactly
+ * that amount. See the HTM_MEM_BUF_SIZE comment for the derivation.
+ */
+ buf->htm_mem_buf = kzalloc_node(HTM_MEM_BUF_SIZE, GFP_KERNEL, cpu_to_node(cpu));
+ if (!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;
}
@@ -877,6 +1078,7 @@ static void htm_free_aux(void *aux)
if (!buf)
return;
+ kfree(buf->htm_mem_buf);
kfree(buf);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH V5 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (3 preceding siblings ...)
2026-08-07 14:37 ` [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
5 siblings, 0 replies; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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, HTM_LLAT=3)
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 three supported HTM event types. Users can pass these names directly
to perf:
# perf record -e htm/htm_core/ ...
# perf record -e htm/htm_nest/ ...
# perf record -e htm/htm_llat/ ...
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V5:
- Document HTM_LLAT=3 in the htm_type format attribute entry.
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 | 36 +++++++++++++++++++
1 file changed, 36 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..141fe8e9952b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-event_source-devices-htm
@@ -0,0 +1,36 @@
+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, HTM_LLAT=3)
+ 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"
+ htm_llat = "htm_type=3"
+
+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/
+ # perf record -e htm/htm_llat/
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
` (4 preceding siblings ...)
2026-08-07 14:37 ` [PATCH V5 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
@ 2026-08-07 14:37 ` Athira Rajeev
2026-08-07 14:45 ` sashiko-bot
5 siblings, 1 reply; 11+ messages in thread
From: Athira Rajeev @ 2026-08-07 14:37 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.tX raw bus-trace AUX data
translation.nX.pX.cX.tX 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 V5:
- Update all output filenames in examples to include the .tX trace-type
suffix (e.g. htm.bin.n0.p2.c0.t1, translation.n0.p2.c0.t1) to match
the new write_htm() naming scheme that disambiguates core HTM
(htm_type=2) from nest HTM (htm_type=1) and HTM_LLAT (htm_type=3)
targets that share the same node/chip/core coordinates.
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.tX and translation.nX.pX.cX.tX), 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..42ad9924a7f6 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**.tX** : raw bus-trace AUX data for node X, chip X, core X
+- **translation.nX.pX.cX**.tX** : memory-configuration records for the same target
+
+.. code-block:: sh
+
+ # perf report
+ # ls htm.bin.* translation.*
+ htm.bin.n0.p2.c0.t1 translation.n0.p2.c0.t1
+
+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::
+
+ htmdecoder <decode_options> htm.bin.n0.p2.c0.t1
+
+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.t1
+ ls translation.* # translation.n0.p2.c0.t1
+ ls perf.data
+
+ # Step 4: Decode the trace
+ htmdecoder <decode_options> htm.bin.n0.p2.c0.t1
+
+Benefits of using HTM interface
=======================================
It is now possible to collect traces for a particular core/chip
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU
2026-08-07 14:37 ` [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
@ 2026-08-07 14:45 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 14:45 UTC (permalink / raw)
To: Athira Rajeev; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Malformed ReST inline markup for output filenames will cause Sphinx warnings.
- [Low] Inconsistent naming of the trace decoding tool ('htmdecode' vs 'htmdecoder').
--
--- Patch [6]: [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU ---
commit f01dd0b2a68c8e69f2fcef07fa210282e973c5a5
Author: Athira Rajeev <atrajeev@linux.ibm.com>
powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU
This commit extends Documentation/arch/powerpc/htm.rst with a new
section detailing the HTM perf PMU interface. It covers how to open
HTM events, explains the required AUX buffer size, details the output
files produced by perf report, and describes how to decode the traces.
> diff --git a/Documentation/arch/powerpc/htm.rst b/Documentation/arch/powerpc/htm.rst
> index fcb4eb6306b16..42ad9924a7f6a 100644
> --- a/Documentation/arch/powerpc/htm.rst
> +++ b/Documentation/arch/powerpc/htm.rst
[ ... ]
> @@ -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
> +========================
[ ... ]
> +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**.tX** : raw bus-trace AUX data for node X, chip X, core X
> +- **translation.nX.pX.cX**.tX** : memory-configuration records for the same target
[Severity: Low]
Can this formatting cause Sphinx to emit warnings? It looks like there is an
extra set of bold markup asterisks trailing the first part of the filenames
that leaves the inline markup unbalanced.
> +
> +.. code-block:: sh
> +
> + # perf report
> + # ls htm.bin.* translation.*
> + htm.bin.n0.p2.c0.t1 translation.n0.p2.c0.t1
> +
> +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::
> +
> + htmdecoder <decode_options> htm.bin.n0.p2.c0.t1
[Severity: Low]
Is the decoding tool named htmdecode or htmdecoder? The descriptive text
refers to htmdecode, but the code block uses htmdecoder.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807143734.1224-1-atrajeev@linux.ibm.com?part=6
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data
2026-08-07 14:37 ` [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
@ 2026-08-07 14:51 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 14:51 UTC (permalink / raw)
To: Athira Rajeev; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] The fallback logic for draining memory configuration records uses an uninitialized stack variable `handle` when `perf_aux_output_begin()` fails, leading to a wild pointer dereference and a kernel crash.
- [High] The `HTM_MEM_BUF_SIZE` calculation assumes a worst-case fixed overhead of 92 bytes, failing to account for dynamic sample sizes when `PERF_SAMPLE_READ` is combined with `PERF_FORMAT_GROUP`. This allows the sample size to exceed 65535, overflowing `perf_event_header.size` and corrupting the ring buffer.
--
--- Patch [4]: [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data ---
commit 46bf60d42ede8a36ee43812e6bfd3aeda1d041a9
Author: Athira Rajeev <atrajeev@linux.ibm.com>
powerpc/perf: Capture the HTM memory configuration as part of perf data
This commit captures the HTM system memory configuration during AUX trace
dumps and emits it as raw perf sample data. It adds a staging buffer for
the hypervisor call and handles drain iteration alongside the AUX data path.
> diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
> index 90d26a721616b..7e8ae35269852 100644
> --- a/arch/powerpc/perf/htm-perf.c
> +++ b/arch/powerpc/perf/htm-perf.c
[ ... ]
> @@ -203,6 +206,168 @@ static ssize_t htm_return_check(int rc)
[ ... ]
> + * 65440 = 32 + 2043 * 32 is the largest multiple of 32 satisfying all
> + * constraints:
> + * - 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 */
[Severity: High]
Does this buffer size properly account for dynamic sample sizes when
PERF_SAMPLE_READ is combined with PERF_FORMAT_GROUP?
If a large group of siblings is configured, the read format payload can
scale up significantly. When htm_collect_memory_config() emits a memory
configuration sample, could the computed size (65440 bytes plus the read
format overhead) exceed 65535?
If it exceeds 65535, wouldn't this overflow the u16 perf_event_header.size,
causing perf_output_begin() to reserve much less space than
perf_output_sample() actually writes, resulting in ring buffer corruption?
[ ... ]
> @@ -594,10 +759,25 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
> * NMI reentrancy from corrupting an outer transaction's handle.
> */
> aux_buf = perf_aux_output_begin(&handle, event);
> - if (!aux_buf)
> + if (!aux_buf) {
> + /*
> + * AUX ring buffer is full: perf_aux_output_begin() returned NULL.
> + * If the AUX trace dump is already complete but memory
> + * configuration collection is still in progress, we must not
> + * return 0 here — that would signal EOF to htm_event_read() and
> + * permanently abandon the mem config drain. Memory config
> + * records go to the main ring buffer via perf_event_overflow(),
> + * which is entirely independent of the AUX ring. Retrieve the
> + * aux_buf from the ring's aux_private and call directly.
> + */
> + struct htm_pmu_buf *fb = perf_get_aux(&handle);
[Severity: Critical]
If perf_aux_output_begin() returns NULL (for instance, if the user didn't
mmap an AUX buffer), does this leave the stack variable handle
uninitialized?
If handle is uninitialized, wouldn't perf_get_aux(&handle) dereference
uninitialized stack memory when it reads handle.event and
handle.rb->aux_priv?
> +
> + if (fb && !fb->collect_htm_trace && fb->collect_htm_mem)
> + return htm_collect_memory_config(event, fb);
> return 0;
> + }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807143734.1224-1-atrajeev@linux.ibm.com?part=4
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data
2026-08-07 14:37 ` [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
@ 2026-08-07 14:57 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 14:57 UTC (permalink / raw)
To: Athira Rajeev; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Applying PAGE_MASK to handle.size incorrectly truncates the available AUX buffer size, causing an infinite loop for 1-page buffers and capacity loss.
- [High] Treating H_PARTIAL as a full success for H_HTM_OP_DUMP_DATA without adjusting the dump length causes trace data corruption.
- [Medium] The driver reads an untrusted power-of-two exponent from the hypervisor and uses it in an unbounded bitwise shift.
--
--- Patch [3]: [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data ---
commit f3228c159ff40b8eb4da824d28f5361ecc14dfbf
Author: Athira Rajeev <atrajeev@linux.ibm.com>
powerpc/perf: Add AUX buffer management to capture HTM trace data
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.
> diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
> index c1ce12605014b..90d26a721616b 100644
> --- a/arch/powerpc/perf/htm-perf.c
> +++ b/arch/powerpc/perf/htm-perf.c
> @@ -277,6 +314,32 @@ static int htm_event_init(struct perf_event *event)
> list_add_tail(&target->list, &htm_active_targets_list);
> mutex_unlock(&htm_targets_lock);
>
> + /*
> + * Query the hardware-allocated HTM buffer size via H_HTM_OP_STATUS.
> + * The status output buffer header byte 0x01 holds
> + * CurrentNestHtmBufferSizeInPowerOf2 (for HTM_NEST) or
> + * CurrentCoreHtmBufferSizeInPowerOf2 (for HTM_CORE); both types
> + * use the same offset (0x01) and length (1 byte).
> + * hw_buf_size is used in htm_dump_sample_data() to bound dump
> + * offsets, preventing H_HTM_OP_DUMP_DATA calls past the end of the
> + * hardware buffer. On failure hw_buf_size stays 0 and the boundary
> + * check is skipped gracefully — the contiguous-window clamp still
> + * applies.
> + */
> + status_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
> + if (status_buf) {
> + do {
> + src = htm_hcall_wrapper(htmflags, cfg.nodeindex,
> + cfg.nodalchipindex, cfg.coreindexonchip,
> + cfg.htmtype, H_HTM_OP_STATUS,
> + virt_to_phys(status_buf), PAGE_SIZE, 0);
> + sret = htm_return_check(src);
> + } while (sret == -EBUSY && ++sretries < MAX_RETRIES);
> + if (sret > 0)
> + target->hw_buf_size = 1ULL << status_buf[0x01];
[Severity: Medium]
Can this shift result in undefined behavior? If the hypervisor returns a value
greater than or equal to 64 for status_buf[0x01], the left shift will invoke
undefined behavior. Should there be a bounds check on the exponent before
performing the shift?
> + kfree(status_buf);
> + }
> +
> event->pmu_private = target;
> event->destroy = reset_htm_active;
> return 0;
[ ... ]
> @@ -510,8 +576,308 @@ 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 perf_output_handle handle;
> + 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;
[ ... ]
> + /*
> + * Assess constraints regarding space remaining across the mapping
> + * context boundary.
> + * handle.size is always page-aligned: perf_aux_output_begin() computes
> + * it as the distance from the write pointer to the wakeup boundary,
> + * rounded to PAGE_SIZE. Masking with PAGE_MASK is therefore a no-op
> + * but is kept to make the page-granularity contract explicit.
> + */
> + chunk_size = handle.size;
> + chunk_size &= PAGE_MASK;
> +
> + if (chunk_size > (aux_buf->size - dump_offset))
> + chunk_size = aux_buf->size - dump_offset;
[Severity: High]
Does masking with PAGE_MASK artificially truncate the available AUX buffer size?
When the perf core allocator prepares a 1-page AUX buffer,
perf_aux_output_begin() computes handle.size as CIRC_SPACE(), which returns at
most size - 1 (e.g., 4095 for a 4096-byte page).
The bitwise AND with PAGE_MASK (~4095) evaluates to 0. For a 1-page buffer,
the usable capacity becomes 0, causing the driver to indefinitely return
-ENOSPC and potentially trapping userspace in an infinite loop.
[ ... ]
> + 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(&handle, chunk_size);
[Severity: High]
If the hypervisor partially completes the dump, does this code advance the
offsets incorrectly?
htm_return_check() maps H_PARTIAL to a success value (1):
arch/powerpc/perf/htm-perf.c:htm_return_check() {
...
switch (rc) {
case H_SUCCESS:
case H_PARTIAL:
return 1;
...
}
Because htm_return_check() discards the actual bytes written, and this
condition assumes the entire chunk_size was written on any success code,
could uninitialized or stale memory be exposed to userspace as valid trace
records, and subsequent chunks be desynchronized?
> + /*
> + * 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);
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807143734.1224-1-atrajeev@linux.ibm.com?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data
2026-08-07 14:37 ` [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
@ 2026-08-07 14:58 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 14:58 UTC (permalink / raw)
To: Athira Rajeev; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing state reset in htm_event_del() causes spurious STOP and DECONFIGURE hcalls on subsequent htm_event_add() failures.
--
--- Patch [1]: [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data ---
commit 482e76cb25a86207519e7500e8ca689e3b600e86
Author: Athira Rajeev <atrajeev@linux.ibm.com>
powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data
This commit adds a new PMU driver for the Hardware Trace Macro (HTM) on
POWER systems. It maps the PMU lifecycle to HTM hypervisor calls and
implements state tracking to handle hcall retries and error conditions.
> diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
> new file mode 100644
> index 0000000000000..cbe4f62813ee0
> --- /dev/null
> +++ b/arch/powerpc/perf/htm-perf.c
[ ... ]
> +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;
[ ... ]
> + htm_event_stop(event, 0);
> +
> + /*
> + * Only issue H_HTM_OP_DECONFIGURE if this driver successfully issued
> + * the paired H_HTM_OP_CONFIGURE. If htm_event_add() failed before or
> + * during CONFIGURE (configured == 0), there is nothing to tear down.
> + * Issuing DECONFIGURE without a prior CONFIGURE would silently destroy
> + * a concurrent trace session that owns the same hardware target and
> + * produce spurious pr_err() noise for an expected H_STATE / H_NOT_AVAILABLE
> + * response.
> + */
> + if (!target->configured)
> + return;
> +
> + /*
> + * 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 */
> +}
[Severity: High]
Does this code need to reset target->configured to 0 after issuing the
H_HTM_OP_DECONFIGURE hcall?
If Process A disables an event via ioctl(PERF_EVENT_IOC_DISABLE),
htm_event_del() correctly deconfigures the hardware but leaves
target->configured as 1.
If Process B then opens and enables the same HTM target, it takes ownership.
If Process A subsequently re-enables its event, the H_HTM_OP_CONFIGURE in
htm_event_add() will fail. When Process A's htm_event_del() runs on the
error path, it will see the stale configured == 1 state and spuriously issue
H_HTM_OP_DECONFIGURE.
Can this silently destroy Process B's active trace?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807143734.1224-1-atrajeev@linux.ibm.com?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-07 14:58 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 14:37 [PATCH V5 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
2026-08-07 14:58 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
2026-08-07 14:57 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
2026-08-07 14:51 ` sashiko-bot
2026-08-07 14:37 ` [PATCH V5 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
2026-08-07 14:37 ` [PATCH V5 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
2026-08-07 14:45 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox