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 <[email protected]>
---
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 189dd40a0b8d..0d1960e299e6 100644
--- a/arch/powerpc/perf/htm-perf.c
+++ b/arch/powerpc/perf/htm-perf.c
@@ -77,9 +77,13 @@ struct htm_config {
  * htm_event_start() and htm_event_stop() to make hcall decisions.
  * event->hw.state is kept in sync for the perf core only.
  */
+static LIST_HEAD(htm_active_targets_list);
+static DEFINE_MUTEX(htm_targets_lock);
+
 struct htm_target_id {
        struct htm_config cfg;
        int tracing_active;             /* HTM_TRACING_ACTIVE / 
HTM_TRACING_INACTIVE */
+       struct list_head list;
 };
 
 /* Helper to parse the 28-bit event config into distinct fields */
@@ -164,7 +168,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;
 }
 
@@ -172,6 +186,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;
@@ -217,11 +232,30 @@ static int htm_event_init(struct perf_event *event)
        }
 
        /* Allocate per-event private state; freed via event->destroy */
-       event->pmu_private = kzalloc(sizeof(struct htm_target_id), GFP_KERNEL);
-       if (!event->pmu_private)
+       target = kzalloc(sizeof(*target), GFP_KERNEL);
+       if (!target)
                return -ENOMEM;
 
-       ((struct htm_target_id *)event->pmu_private)->cfg = cfg;
+       target->cfg = cfg;
+       target->tracing_active = HTM_TRACING_INACTIVE;
+       INIT_LIST_HEAD(&target->list);
+
+       mutex_lock(&htm_targets_lock);
+       list_for_each_entry(tmp, &htm_active_targets_list, list) {
+               if (tmp->cfg.htmtype == cfg.htmtype &&
+                       tmp->cfg.nodeindex == cfg.nodeindex &&
+                       tmp->cfg.nodalchipindex == cfg.nodalchipindex &&
+                       tmp->cfg.coreindexonchip == cfg.coreindexonchip) {
+                       mutex_unlock(&htm_targets_lock);
+                       kfree(target);
+                       return -EBUSY;
+               }
+       }
+
+       list_add_tail(&target->list, &htm_active_targets_list);
+       mutex_unlock(&htm_targets_lock);
+
+       event->pmu_private = target;
        event->destroy = reset_htm_active;
        return 0;
 }
-- 
2.43.0


Reply via email to