From: Athira Rajeev <atrajeev@linux.ibm.com>
To: linuxppc-dev@lists.ozlabs.org, maddy@linux.ibm.com
Cc: linux-perf-users@vger.kernel.org, atrajeev@linux.ibm.com,
hbathini@linux.vnet.ibm.com, tejas05@linux.ibm.com,
venkat88@linux.ibm.com, tshah@linux.ibm.com, usha.r2@ibm.com
Subject: [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data
Date: Sat, 25 Jul 2026 12:29:39 +0530 [thread overview]
Message-ID: <20260725065942.78839-4-atrajeev@linux.ibm.com> (raw)
In-Reply-To: <20260725065942.78839-1-atrajeev@linux.ibm.com>
Implement support for auxiliary (AUX) ring buffers in the HTM PMU driver.
This enables high-volume trace data to be streamed directly into a perf
AUX buffer for deferred post-processing by the perf tool.
Introduce the PMU data structures htm_pmu_buf and htm_pmu_ctx, and
implement the core lifecycle hooks:
htm_setup_aux(): Allocates per-CPU context and records the AUX buffer
base, head, and size for the active trace window.
htm_free_aux(): Releases the PMU-private tracking allocations.
htm_pmu_ctx embeds a struct perf_output_handle as a per-CPU variable,
following the established upstream pattern. Reentrancy
is handled by perf_aux_output_begin() itself via rb->aux_nest: a nested
caller (e.g. from NMI context) receives NULL and returns immediately
without touching the outer handle.
Physical contiguity requirement:
The perf core AUX allocator (rb_alloc_aux) may return a page array with
physical fragmentation gaps. H_HTM_OP_DUMP_DATA operates on raw
physical addresses and requires a strictly contiguous region; crossing
a gap would cause silent memory corruption.
To prevent this, opt into PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
to request large, physically contiguous allocations, and perform a
page-by-page physical continuity scan in htm_event_read() before each
H_HTM_OP_DUMP_DATA call. The scan verifies that
virt_to_phys(page[n]) + PAGE_SIZE == virt_to_phys(page[n+1]) and
breaks the loop on the first discontinuity, ensuring the dump to the
verified safe window.
htm_dump_sample_data() returns the record count directly (already
divided by the per-format record size) so htm_event_read() uses the
value without format knowledge:
- AUX trace path: chunk_size / 128 (128-byte HTM records)
- Memory cfg path: to_copy / 32 (32-byte entries, patch 4)
htm_event_read() sets event->count as follows:
- ret > 0: record count written; used directly as event->count.
- ret == -ENOSPC (AUX ring buffer full): event->count = 1 so the
perf tool drain loop keeps retrying after the consumer drains the
buffer.
- all other non-success paths: event->count = 0 so the drain loop
stops cleanly.
event->count does not represent an instruction or cycle count; actual
trace records are decoded in userspace by the perf tool.
HTM target identity and tracing state are kept in event->pmu_private
(htm_target_id). AUX-private state holds only buffer metadata and
dump progress; htm_target_id.tracing_active remains the source for
tracing state.
Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Added a dedicated Physical contiguity requirement section and a full
per-condition htm_dump_sample_data() return values and event->count
table in the commit body, covering all five exit paths (success,
-ENOSPC, H_NOT_AVAILABLE, H_LONG_BUSY_*/hard error, stop-failed
before dump). V2 had a brief paragraph.
- htm_dump_sample_data() return type changed from int to ssize_t; ret
locals in htm_dump_sample_data() and htm_event_read() likewise changed
to ssize_t. Prevents truncation of the upper 32 bits of chunk_size on
64-bit PowerPC.
- Success path now returns (ssize_t)(chunk_size / 128), the number
128-byte HTM trace records, rather than raw chunk_size bytes.
htm_event_read() uses the value directly (local64_set(&event->count,
ret)) without any further division, keeping it free of format
knowledge. The same contract is extended to the memory-config path
in patch 4 (to_copy / 32).
- Removed the unused trace_records field from struct htm_pmu_buf and
its initialisation and increment sites. V2 carried the field as
dead code.
- Added event->count does not represent an instruction or cycle count
sentence to both the commit body and the in-code comment
in htm_event_read().
Changes in V2:
- Opted into PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE to
request physically contiguous allocations, which H_HTM_OP_DUMP_DATA
requires. V1 had no contiguity requirement and could silently
corrupt memory if the allocator returned a fragmented page array.
- Added a page-by-page physical-continuity scan in htm_event_read()
before every H_HTM_OP_DUMP_DATA call. The scan verifies that
virt_to_phys(page[n]) + PAGE_SIZE == virt_to_phys(page[n+1]) and
stops at the first gap, bounding the dump to a verified safe window.
- htm_dump_sample_data() now returns a record count (already divided
by the per-format record size) rather than raw bytes, so
htm_event_read() uses the value directly without format knowledge.
AUX trace path returns chunk_size / 128; the memory config path
added in patch 4 will return to_copy / 32. Three-way semantics:
ret > 0 -> count = ret; ret == -ENOSPC -> count = 1; otherwise
count = 0. The V1 arch_record__collect_final_data callback loop
is replaced by this mechanism.
- Removed the unused trace_records field from htm_pmu_buf; htm_dump_
sample_data() now returns chunk_size on success so htm_event_read()
can derive the record count directly.
- Introduced distinct htm_pmu_buf and htm_pmu_ctx structures; V1 used a
single struct htm_pmu_buf for both purposes.
arch/powerpc/perf/htm-perf.c | 250 ++++++++++++++++++++++++++++++++++-
1 file changed, 249 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index 84a5601ee7f7..f880a5fc8833 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -95,6 +95,22 @@ static inline void parse_htm_config(u64 config, struct htm_config *cfg)
cfg->coreindexonchip = (config >> 20) & 0xff;
}
+struct htm_pmu_buf {
+ int nr_pages;
+ bool snapshot;
+ void *base;
+ void **pages;
+ u64 head;
+ u64 size;
+ int collect_htm_trace;
+};
+
+struct htm_pmu_ctx {
+ struct perf_output_handle handle;
+};
+
+static DEFINE_PER_CPU(struct htm_pmu_ctx, htm_pmu_ctx);
+
/*
* Check the return code for H_HTM hcall.
* Return 1 if either H_PARTIAL or H_SUCCESS is returned.
@@ -415,8 +431,237 @@ static void htm_event_del(struct perf_event *event, int flags)
/* pmu_private freed by event->destroy = reset_htm_active */
}
+static ssize_t htm_dump_sample_data(struct perf_event *event)
+{
+ struct htm_pmu_ctx *htm_ctx = this_cpu_ptr(&htm_pmu_ctx);
+ struct htm_target_id *target = event->pmu_private;
+ struct htm_pmu_buf *aux_buf;
+ struct htm_config cfg = target->cfg;
+ u64 chunk_size, dump_offset, page_index, page_offset;
+ u64 max_contiguous_bytes, expected_phys, scan_index, actual_phys;
+ u64 hypervisor_target_phys;
+ void *target_page_virt;
+ ssize_t ret = 0;
+ int retries = 0;
+ long rc;
+
+ /* Start AUX transaction session framework */
+ aux_buf = perf_aux_output_begin(&htm_ctx->handle, event);
+ if (!aux_buf)
+ return 0;
+
+ if (!aux_buf->collect_htm_trace) {
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return 0;
+ }
+
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ htm_event_stop(event, 0);
+ if (target->tracing_active == HTM_TRACING_ACTIVE) {
+ /*
+ * H_HTM_OP_STOP failed.
+ * Cannot dump data while the trace is still running.
+ * Return -EIO to signal a non-retriable hardware failure;
+ * htm_event_read() will set event->count=0 and the drain
+ * loop will stop cleanly.
+ */
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return -EIO;
+ }
+ }
+
+ /* Derive the exact target destination point directly out of active ring pointers */
+ dump_offset = htm_ctx->handle.head & (aux_buf->size - 1);
+ page_index = dump_offset >> PAGE_SHIFT;
+ page_offset = dump_offset & (PAGE_SIZE - 1);
+
+ /*
+ * Assess constraints regarding space remaining across the mapping
+ * context boundary
+ */
+ chunk_size = htm_ctx->handle.size;
+ chunk_size &= PAGE_MASK;
+
+ if (chunk_size > (aux_buf->size - dump_offset))
+ chunk_size = aux_buf->size - dump_offset;
+
+ /*
+ * HTM driver uses these capabilities:
+ * PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
+ * the core perf ring-buffer allocator (rb_alloc_aux) tries to allocate
+ * physically contiguous page block. If not available, it tries to allocate
+ * largest possible contiguous block.
+ *
+ * Example: If we ask perf for 1024 pages (64MB), the kernel executes a loop
+ * inside rb_alloc_aux() to fulfill that request using the buddy allocator. it
+ * always tries to grab the largest possible contiguous block of memory it can
+ * find first, then takes the next largest, and repeats until request is
+ * completely filled. Here while writing to aux buffer, to eliminate any
+ * virtual or physical boundary overruns, check for the page boundary.
+ *
+ * Dynamically scan forward page-by-page from our active page_index to
+ * calculate the absolute boundary limit of this current physically
+ * contiguous block chunk. Prevents hypervisor macro overruns across
+ * asymmetrical fragmentation gaps.
+ */
+ max_contiguous_bytes = PAGE_SIZE - page_offset;
+ scan_index = page_index + 1;
+ expected_phys = (u64)virt_to_phys(aux_buf->pages[page_index]) + PAGE_SIZE;
+
+ while (scan_index < aux_buf->nr_pages && max_contiguous_bytes < chunk_size) {
+ actual_phys = (u64)virt_to_phys(aux_buf->pages[scan_index]);
+
+ if (actual_phys != expected_phys)
+ break; /* Intersected a fragmentation boundary block link! */
+
+ max_contiguous_bytes += PAGE_SIZE;
+ expected_phys += PAGE_SIZE;
+ scan_index++;
+ }
+
+ /* Bound transfer length tightly within the validated contiguous window */
+ if (chunk_size > max_contiguous_bytes)
+ chunk_size = max_contiguous_bytes;
+
+ if (!chunk_size) {
+ /*
+ * No space in the AUX ring buffer right now. Leave
+ * collect_htm_trace set. Return -ENOSPC so htm_event_read()
+ * keeps event->count non-zero, signalling to the caller that
+ * collection is still ongoing and another pmu->read pass
+ * should be attempted once the consumer has drained the buffer.
+ */
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return -ENOSPC;
+ }
+
+ /*
+ * Compute the precise base target address using
+ * localized page offset rules
+ */
+ target_page_virt = aux_buf->pages[page_index];
+ hypervisor_target_phys = (u64)virt_to_phys(target_page_virt) + page_offset;
+
+ do {
+ /*
+ * Invoke H_HTM call with:
+ * - operation as htm dump (H_HTM_OP_DUMP_DATA)
+ * - last three values are address, size and offset
+ */
+ rc = htm_hcall_wrapper(htmflags, cfg.nodeindex, cfg.nodalchipindex,
+ cfg.coreindexonchip, cfg.htmtype, H_HTM_OP_DUMP_DATA,
+ hypervisor_target_phys, chunk_size, aux_buf->head);
+ ret = htm_return_check(rc);
+ } while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+ if (ret > 0) {
+ aux_buf->head += chunk_size;
+ perf_aux_output_end(&htm_ctx->handle, chunk_size);
+ /*
+ * Return the number of 128-byte HTM trace records written.
+ * Dividing here keeps htm_event_read() free of format
+ * knowledge: it can simply use the returned count directly,
+ * regardless of which data path (AUX trace or memory config)
+ * produced it.
+ */
+ return (ssize_t)(chunk_size / 128);
+ }
+
+ /*
+ * Hcall failed. All non-success paths end collection for this
+ * buffer session.
+ */
+ aux_buf->collect_htm_trace = 0;
+ perf_aux_output_end(&htm_ctx->handle, 0);
+ return ret;
+}
+
static void htm_event_read(struct perf_event *event)
{
+ ssize_t ret;
+
+ ret = htm_dump_sample_data(event);
+ /*
+ * htm_dump_sample_data() returns the record count directly
+ * (already divided by the per-format record size):
+ * AUX trace path: chunk_size / 128 (128-byte HTM records)
+ * Memory cfg path: to_copy / 32 (32-byte entries)
+ *
+ * ret > 0: record count written; use directly as event->count.
+ * ret == -ENOSPC: AUX buffer full, hypervisor stream intact;
+ * count = 1 so the drain loop keeps retrying
+ * once the consumer has drained the buffer.
+ * ret <= 0: EOF, stop failed, or hard error; count = 0
+ * so the drain loop stops cleanly.
+ *
+ * event->count does not represent an instruction or cycle count;
+ * actual trace records are decoded in userspace by the perf tool.
+ */
+ if (ret > 0)
+ local64_set(&event->count, ret);
+ else if (ret == -ENOSPC)
+ local64_set(&event->count, 1);
+ else
+ local64_set(&event->count, 0);
+}
+
+/*
+ * Set up pmu-private data structures for an AUX area
+ * **pages contains the aux buffer allocated for this event
+ * for the corresponding cpu. rb_alloc_aux uses "alloc_pages_node"
+ * and returns pointer to each page address.
+ * PMU capabilities: PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE
+ * to try get closest possible physically contiguous page blocks.
+ *
+ * The aux private data structure ie, "struct htm_pmu_buf" mainly
+ * saves
+ * - buf->base: aux buffer base address
+ * - buf->head: offset from base address where data will be written to.
+ * - buf->size: Size of allocated memory
+ */
+static void *htm_setup_aux(struct perf_event *event, void **pages,
+ int nr_pages, bool snapshot)
+{
+ int cpu = event->cpu;
+ struct htm_pmu_buf *buf;
+
+ if (!nr_pages)
+ return NULL;
+
+ if (cpu == -1)
+ cpu = raw_smp_processor_id();
+
+ buf = kzalloc_node(sizeof(*buf), GFP_KERNEL, cpu_to_node(cpu));
+ if (!buf)
+ return NULL;
+
+ buf->nr_pages = nr_pages;
+ buf->snapshot = snapshot;
+ buf->size = (u64)nr_pages << PAGE_SHIFT;
+ buf->pages = pages;
+
+ buf->base = pages[0];
+ if (!buf->base) {
+ kfree(buf);
+ return NULL;
+ }
+
+ buf->collect_htm_trace = 1;
+ buf->head = 0;
+ return buf;
+}
+
+/*
+ * free pmu-private AUX data structures
+ */
+static void htm_free_aux(void *aux)
+{
+ struct htm_pmu_buf *buf = aux;
+
+ if (!buf)
+ return;
+
+ kfree(buf);
}
static struct pmu htm_pmu = {
@@ -429,7 +674,10 @@ static struct pmu htm_pmu = {
.read = htm_event_read,
.start = htm_event_start,
.stop = htm_event_stop,
- .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_EXCLUSIVE,
+ .setup_aux = htm_setup_aux,
+ .free_aux = htm_free_aux,
+ .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_EXCLUSIVE
+ | PERF_PMU_CAP_AUX_NO_SG | PERF_PMU_CAP_AUX_PREFER_LARGE,
};
static int htm_init(void)
--
2.43.0
next prev parent reply other threads:[~2026-07-25 7:00 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-25 6:59 [PATCH V3 0/6] powerpc/perf: Add HTM PMU driver and perf AUX support Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 1/6] powerpc/perf: Add HTM PMU driver to expose Hardware Trace Macro data Athira Rajeev
2026-07-25 7:45 ` sashiko-bot
2026-07-25 6:59 ` [PATCH V3 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
2026-07-25 6:59 ` Athira Rajeev [this message]
2026-07-25 7:45 ` [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data sashiko-bot
2026-07-25 6:59 ` [PATCH V3 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data Athira Rajeev
2026-07-25 7:43 ` sashiko-bot
2026-07-25 6:59 ` [PATCH V3 5/6] docs: ABI: sysfs-bus-event_source-devices-htm: Document sysfs event format entries for htm pmu Athira Rajeev
2026-07-25 6:59 ` [PATCH V3 6/6] powerpc/perf/htm: Add documentation for Hardware Trace Macro PMU Athira Rajeev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260725065942.78839-4-atrajeev@linux.ibm.com \
--to=atrajeev@linux.ibm.com \
--cc=hbathini@linux.vnet.ibm.com \
--cc=linux-perf-users@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=maddy@linux.ibm.com \
--cc=tejas05@linux.ibm.com \
--cc=tshah@linux.ibm.com \
--cc=usha.r2@ibm.com \
--cc=venkat88@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.