LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
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 4/6] powerpc/perf: Capture the HTM memory configuration as part of perf data
Date: Sat, 25 Jul 2026 12:29:40 +0530	[thread overview]
Message-ID: <20260725065942.78839-5-atrajeev@linux.ibm.com> (raw)
In-Reply-To: <20260725065942.78839-1-atrajeev@linux.ibm.com>

The H_HTM hypervisor call can also return the system memory
configuration, which describes the physical to logical real address
mapping for logical partitions.

After dumping HTM trace data into the AUX buffer, capture the
corresponding HTM system memory configuration records and emit them as
raw perf sample data for userspace parsing.

Add AUX-private tracking for a hypervisor memory-configuration dump
buffer, a stable emit buffer, and iterator state. Once AUX trace dumping
completes, htm_event_read() switches to H_HTM_OP_DUMP_SYSMEM_CONF and
iterates over the returned records until the configuration stream is
exhausted.

Trace payload continues to be written to the AUX buffer, while memory
configuration records are emitted as raw perf samples. This keeps the
AUX stream focused on trace data and allows the configuration records to
be decoded separately in userspace.

The hypervisor fills as many 32-byte entries as fit within the buffer
size passed to H_HTM_OP_DUMP_SYSMEM_CONF — it does not cap at a fixed
entry count.  Observed maximum fill is 64480 bytes (2015 entries at
32 bytes each plus a 32-byte header).  HTM_MEM_BUF_SIZE is therefore
defined as the allocation size (65440 bytes) and HTM_MEM_MAX_ENTRIES is
derived from it ((HTM_MEM_BUF_SIZE - 32) / 32 = 2043), not the other
way around.  This ensures the hcall is always told the true buffer size
and the WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE) guard is a genuine
impossibility check rather than a post-overflow assertion.

HTM_MEM_BUF_SIZE = 65440 is the largest multiple of 32 that satisfies
both constraints: it exceeds the observed 64480-byte maximum fill by 960
bytes of headroom, and the resulting perf record (65440 + 92 bytes of
fixed overhead = 65532) stays below 65535, the __u16 limit of
perf_event_header.size.  The 92-byte overhead is: 8 (perf_event_header)
+ 64 (header_size worst case: 8 u64 sample fields) + 16 (id_header_size
worst case) + 4 (PERF_SAMPLE_RAW u32 size prefix).

perf_fetch_caller_regs() is used to initialise the pt_regs argument
passed to perf_event_overflow().  An uninitialised stack frame would
leak kernel stack bytes to userspace if the event is opened with
PERF_SAMPLE_REGS_INTR.  This follows the pattern used by tracepoints
and BPF perf-event helpers for synthetic sample emission.

When perf_event_overflow() throttles the event (returns non-zero), the
iterator mem_start is not advanced.  The same memory configuration block
will be retried on the next htm_event_read() call once the event is
unthrottled.  collect_htm_mem is left set so the retry path is entered;
ENOSPC is returned so event->count is set to 1 and the drain loop
keeps retrying until the ring buffer consumer catches up.

Keep HTM tracing state in event->pmu_private via htm_target_id. AUX
private state is used only for dump progress and staging buffers.

Signed-off-by: Athira Rajeev <atrajeev@linux.ibm.com>
---
Changes in V3:
- Fixed HTM_MEM_BUF_SIZE defined as (32 + 2013 * 32 = 64448 bytes) while
  the hypervisor actually fills up to 64480 bytes (2015 entries) when
  given a sufficiently large buffer — overflowing the allocation by 32
  bytes.  The hypervisor fills as many entries as fit within the given
  buffer size; it does not cap at a fixed entry count.  Fixed by
  redefining HTM_MEM_BUF_SIZE as 65440U (the largest multiple of 32
  fitting in perf_event_header.size __u16 with 92 bytes of worst-case
  header overhead: 65440 + 92 = 65532 < 65535) and deriving
  HTM_MEM_MAX_ENTRIES from it ((HTM_MEM_BUF_SIZE - 32) / 32 = 2043).
  The buffer now covers the observed maximum with 960 bytes headroom,
  and the WARN_ON_ONCE guard is a genuine impossibility check.
- Fixed htm_mem_buf allocated as PAGE_SIZE (4096 bytes on 4K-page
  configs) while the hypervisor was told the buffer length is
  HTM_MEM_BUF_SIZE.  Changed to kmalloc_node with HTM_MEM_BUF_SIZE.
- Fixed uninitialized struct pt_regs regs passed to perf_event_overflow().
  If the event is opened with PERF_SAMPLE_REGS_INTR the perf core reads
  these bytes into the ring buffer, leaking kernel stack memory to
  userspace.  Added perf_fetch_caller_regs(&regs) after the declaration
  block, following the pattern in kernel/trace/trace_event_perf.c and
  kernel/trace/bpf_trace.c.
- Clarified the perf_event_overflow() throttle path: when the event is
  throttled, mem_start is intentionally not advanced (the same block will
  be retried on the next drain pass once unthrottled).  Added a comment
  making this explicit and distinguishing it from the error/EOF paths
  that clear collect_htm_mem.

Changes in V2:
- Memory configuration records are now emitted as PERF_SAMPLE_RAW
  samples directly by htm_event_read() after the AUX trace dump
  completes, using H_HTM_OP_DUMP_SYSMEM_CONF.  V1 embedded the
  configuration data inside the AUX buffer itself and used two
  PERF_SAMPLE_RAW boundary markers (start/end) to delimit it.
- The two-marker boundary scheme is removed.  There is no longer any
  interleaving of memory configuration data inside the AUX stream;
  the AUX buffer carries only bus-trace data.
- Separate AUX-private fields for a hypervisor dump buffer, a stable
  emit buffer, and iterator state are introduced to manage the
  multi-call SYSMEM_CONF dump loop.
- Tracing state remains in event->pmu_private (htm_target_id); AUX
  private state is used only for dump progress and staging buffers,
  consistent with the restructuring in patches 1 and 3.

 arch/powerpc/perf/htm-perf.c | 205 ++++++++++++++++++++++++++++++++++-
 1 file changed, 204 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/perf/htm-perf.c b/arch/powerpc/perf/htm-perf.c
index f880a5fc8833..af9a8412ff20 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -103,6 +103,10 @@ struct htm_pmu_buf {
 	u64	head;
 	u64     size;
 	int     collect_htm_trace;
+	void    *htm_mem_buf;	/* Staging bounce area allocated node-locally */
+	void	*emit_buf;	/* Stable buffer for perf raw sample emission */
+	u64     mem_start;	/* Hypervisor offset state iterator tracker */
+	int     collect_htm_mem;	/* State flag tracking whether memory logging is ongoing */
 };
 
 struct htm_pmu_ctx {
@@ -173,6 +177,174 @@ static ssize_t htm_return_check(int rc)
 #define HTM_TRACING_ACTIVE	1
 #define HTM_TRACING_INACTIVE	0
 
+/*
+ * HTM_MEM_BUF_SIZE is the allocation size for the hcall staging buffer.
+ * The hypervisor fills as many 32-byte entries as fit within the buffer
+ * size passed to H_HTM_OP_DUMP_SYSMEM_CONF — it does not cap at a fixed
+ * entry count.  Observed maximum fill is 64480 bytes (2015 entries).
+ *
+ * HTM_MEM_BUF_SIZE is chosen to satisfy two hard constraints:
+ *
+ *   1. Must cover the observed maximum fill:
+ *        HTM_MEM_BUF_SIZE >= 64480  (2015 * 32 + 32-byte header)
+ *
+ *   2. The full perf record (perf_event_header + fixed sample fields +
+ *      PERF_SAMPLE_RAW u32 size prefix + to_copy) must fit in
+ *      perf_event_header.size which is __u16 (max 65535):
+ *        overhead = 8 (perf_event_header)
+ *                 + 64 (header_size, worst case: 8 u64 sample fields)
+ *                 + 16 (id_header_size, worst case)
+ *                 +  4 (PERF_SAMPLE_RAW u32 size prefix)
+ *                 = 92 bytes
+ *        to_copy <= 65535 - 92 = 65443
+ *        round down to multiple of 32: 65440
+ *
+ *   3. HTM_MEM_BUF_SIZE must be a multiple of 32 so a whole number of
+ *      32-byte entries fill it exactly.
+ *
+ * 65440 = 32 + 2043 * 32 is the largest multiple of 32 satisfying all
+ * three constraints:
+ *   - covers 64480 with 960 bytes headroom
+ *   - total record: 65440 + 92 = 65532 < 65535 (3-byte u16 margin)
+ *
+ * HTM_MEM_MAX_ENTRIES is derived from HTM_MEM_BUF_SIZE — not the other
+ * way around — so the hcall is always given the true buffer size and
+ * the WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE) guard is a genuine
+ * impossibility check rather than a post-overflow assertion.
+ */
+#define HTM_MEM_BUF_SIZE	65440U
+#define HTM_MEM_MAX_ENTRIES	((HTM_MEM_BUF_SIZE - 32) / 32)	/* 2043 */
+
+/*
+ * htm_collect_memory_config - drain H_HTM_OP_DUMP_SYSMEM_CONF into the
+ * perf ring buffer as PERF_SAMPLE_RAW records.
+ *
+ * Returns the number of 32-byte memory configuration entries emitted
+ * (to_copy / 32) on success, 0 if the event was throttled or the stream
+ * ended normally, or a negative error code on hard failure.  The caller
+ * (htm_dump_sample_data) uses the return value directly as event->count,
+ * consistent with the AUX trace path returning chunk_size / 128.
+ */
+static ssize_t htm_collect_memory_config(struct perf_event *event,
+					 struct htm_pmu_buf *aux_buf)
+{
+	struct perf_sample_data data;
+	struct perf_raw_record raw;
+	struct pt_regs regs;
+	u8 *htm_mem_buf = aux_buf->htm_mem_buf;
+	__be64 *num_entries;
+	u64 next_start;
+	u64 to_copy;
+	void *emit_buf = aux_buf->emit_buf;
+	long rc;
+	ssize_t ret = 0;
+	int retries;
+
+	/*
+	 * Initialise regs to the current caller context.  perf_event_overflow()
+	 * may sample register state into the ring buffer if the event was
+	 * opened with PERF_SAMPLE_REGS_INTR; an uninitialised stack frame
+	 * would leak kernel stack bytes to userspace.  Use
+	 * perf_fetch_caller_regs() to capture a safe, deterministic snapshot
+	 * of the current CPU state — the same pattern used by tracepoints and
+	 * BPF perf-event helpers for synthetic sample emission.
+	 */
+	perf_fetch_caller_regs(&regs);
+
+	/*
+	 * htm_mem_buf is the hcall memory buffer target reused each iteration.
+	 * emit_buf is a preallocated stable buffer used for perf raw
+	 * sample emission, so raw.frag.data remains valid during
+	 * perf_event_overflow().
+	 * Size: 32-byte header + HTM_MEM_MAX_ENTRIES * 32-byte entries.
+	 */
+	while (true) {
+		retries = 0;
+		do {
+			rc = htm_hcall_wrapper(htmflags, 0, 0, 0,
+					       0, H_HTM_OP_DUMP_SYSMEM_CONF,
+					       virt_to_phys(aux_buf->htm_mem_buf),
+					       HTM_MEM_BUF_SIZE, aux_buf->mem_start);
+			ret = htm_return_check(rc);
+		} while (ret == -EBUSY && ++retries < MAX_RETRIES);
+
+		/*
+		 * ret == 0 (H_NOT_AVAILABLE): normal end of stream — clear
+		 *   collect_htm_mem so the next read does not re-enter.
+		 * ret < 0 (error): hard failure — clear collect_htm_mem and
+		 *   stop.
+		 * Both cases are treated as "no more data for this session".
+		 */
+		if (ret <= 0) {
+			aux_buf->collect_htm_mem = 0;
+			break;
+		}
+
+		/*
+		 * Read next iterator value from the hcall response BEFORE
+		 * emitting — but only commit it to mem_start after a
+		 * successful write of raw data.
+		 */
+		next_start = be64_to_cpu(*((__be64 *)(htm_mem_buf + 0x8)));
+
+		/*
+		 * The hcall was given HTM_MEM_BUF_SIZE bytes of buffer space,
+		 * so num_entries is already bounded to HTM_MEM_MAX_ENTRIES.
+		 * The response is a complete hcall data:
+		 *   [32-byte header][num_entries * 32-byte entries]
+		 * Userspace can validate and parse it directly.
+		 */
+		num_entries = (__be64 *)(htm_mem_buf + 0x10);
+		to_copy = 32 + (be64_to_cpu(*num_entries) * 32);
+		if (WARN_ON_ONCE(to_copy > HTM_MEM_BUF_SIZE)) {
+			ret = -EIO;
+			aux_buf->collect_htm_mem = 0;
+			break;
+		}
+
+		memcpy(emit_buf, aux_buf->htm_mem_buf, to_copy);
+
+		perf_sample_data_init(&data, 0, event->hw.last_period);
+		memset(&raw, 0, sizeof(raw));
+		raw.frag.data = emit_buf;
+		raw.frag.size = to_copy;
+		perf_sample_save_raw_data(&data, event, &raw);
+
+		if (perf_event_overflow(event, &data, &regs)) {
+			/*
+			 * Event throttled: the record was not written to the
+			 * ring buffer.  Do NOT advance mem_start — the same
+			 * block will be retried on the next htm_event_read()
+			 * call once the event is unthrottled.  Leave
+			 * collect_htm_mem set so the retry path is entered.
+			 * Return -ENOSPC so htm_event_read() sets event->count=1,
+			 * keeping the drain loop alive until the ring buffer
+			 * consumer catches up.
+			 */
+			ret = -ENOSPC;
+			break;
+		}
+
+		/* Record written successfully: advance the iterator */
+		aux_buf->mem_start = next_start;
+
+		/*
+		 * Return the number of 32-byte memory configuration entries
+		 * in this batch (to_copy / 32).  Dividing here keeps
+		 * htm_event_read() free of format knowledge, consistent with
+		 * the AUX trace path returning chunk_size / 128.
+		 */
+		ret = (ssize_t)(to_copy / 32);
+
+		if (!next_start) {
+			aux_buf->collect_htm_mem = 0;
+			break;
+		}
+	}
+
+	return ret;
+}
+
 static void reset_htm_active(struct perf_event *event)
 {
 	struct htm_target_id *target = event->pmu_private;
@@ -450,7 +622,7 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
 	if (!aux_buf)
 		return 0;
 
-	if (!aux_buf->collect_htm_trace) {
+	if (!aux_buf->collect_htm_trace && !aux_buf->collect_htm_mem) {
 		perf_aux_output_end(&htm_ctx->handle, 0);
 		return 0;
 	}
@@ -470,6 +642,11 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
 		}
 	}
 
+	if (!aux_buf->collect_htm_trace) {
+		ret = htm_collect_memory_config(event, aux_buf);
+		goto out;
+	}
+
 	/* Derive the exact target destination point directly out of active ring pointers */
 	dump_offset = htm_ctx->handle.head & (aux_buf->size - 1);
 	page_index = dump_offset >> PAGE_SHIFT;
@@ -572,6 +749,8 @@ static ssize_t htm_dump_sample_data(struct perf_event *event)
 	 * buffer session.
 	 */
 	aux_buf->collect_htm_trace = 0;
+	ret = htm_collect_memory_config(event, aux_buf);
+out:
 	perf_aux_output_end(&htm_ctx->handle, 0);
 	return ret;
 }
@@ -646,7 +825,29 @@ static void *htm_setup_aux(struct perf_event *event, void **pages,
 		return NULL;
 	}
 
+	/*
+	 * htm_mem_buf is the staging area passed directly to the
+	 * H_HTM_OP_DUMP_SYSMEM_CONF hcall.  The hypervisor is told the
+	 * buffer length is HTM_MEM_BUF_SIZE (65440 bytes); allocate exactly
+	 * that amount.  See the HTM_MEM_BUF_SIZE comment for the derivation.
+	 */
+	buf->htm_mem_buf = kmalloc_node(HTM_MEM_BUF_SIZE, GFP_KERNEL, cpu_to_node(cpu));
+	if (!buf->htm_mem_buf) {
+		kfree(buf);
+		return NULL;
+	}
+
+	buf->emit_buf = kmalloc_node(HTM_MEM_BUF_SIZE, GFP_KERNEL,
+			cpu_to_node(cpu));
+	if (!buf->emit_buf) {
+		kfree(buf->htm_mem_buf);
+		kfree(buf);
+		return NULL;
+	}
+
 	buf->collect_htm_trace = 1;
+	buf->collect_htm_mem = 1;
+	buf->mem_start = 0;
 	buf->head = 0;
 	return buf;
 }
@@ -661,6 +862,8 @@ static void htm_free_aux(void *aux)
 	if (!buf)
 		return;
 
+	kfree(buf->emit_buf);
+	kfree(buf->htm_mem_buf);
 	kfree(buf);
 }
 
-- 
2.43.0



  parent reply	other threads:[~2026-07-25  7:28 UTC|newest]

Thread overview: 7+ 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  6:59 ` [PATCH V3 2/6] powerpc/perf: Reject duplicate HTM target reservations Athira Rajeev
2026-07-25  6:59 ` [PATCH V3 3/6] powerpc/perf: Add AUX buffer management to capture HTM trace data Athira Rajeev
2026-07-25  6:59 ` Athira Rajeev [this message]
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-5-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox