Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176488407
D57631.id185746.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
73 KB
Referenced Files
None
Subscribers
None
D57631.id185746.diff
View Options
diff --git a/sys/dev/hwpmc/hwpmc_pmu.h b/sys/dev/hwpmc/hwpmc_pmu.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/hwpmc/hwpmc_pmu.h
@@ -0,0 +1,306 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * This is the PMU event grouping and multiplexing interface.
+ * It defers hardware row allocation until the group starts.
+ */
+
+#ifndef _DEV_HWPMC_PMU_H_
+#define _DEV_HWPMC_PMU_H_
+
+#include <sys/mutex.h>
+#include <sys/pmc.h>
+#include <sys/queue.h>
+
+#define PMC_ROW_IS_UNASSIGNED(P) (PMC_TO_ROWINDEX(P) == PMC_ROW_UNASSIGNED)
+
+#ifdef _KERNEL
+
+/*
+ * These are the flags for process and thread descriptor lookups.
+ */
+enum pmc_flags {
+ PMC_FLAG_NONE = 0x00, /* do nothing */
+ PMC_FLAG_REMOVE = 0x01, /* atomically remove entry from hash */
+ PMC_FLAG_ALLOCATE = 0x02, /* add entry to hash if not found */
+ PMC_FLAG_NOWAIT = 0x04, /* do not wait for mallocs */
+};
+
+/*
+ * Scheduling constraint from the class backend.
+ * pc_allowed_rows is a bitmask of valid class row indices.
+ */
+#define PMC_SC_F_FIXED 0x0001 /* must use pc_fixed_row */
+#define PMC_SC_F_SHARED 0x0004 /* tolerates sharing siblings */
+
+struct pmc_sched_constraint {
+ uint32_t pc_allowed_rows; /* bitmask of legal rows */
+ uint8_t pc_weight; /* popcount(pc_allowed_rows) */
+ uint8_t pc_fixed_row; /* used iff PMC_SC_F_FIXED */
+ uint16_t pc_flags; /* PMC_SC_F_* */
+};
+typedef struct pmc_sched_constraint pmc_sched_constraint_t;
+
+/* This struct wraps an event for group allocation state. */
+struct pmu_event {
+ TAILQ_ENTRY(pmu_event) pe_sibling;
+ pmu_group_t *pe_group;
+ struct pmc *pe_pmc;
+ counter_u64_t pe_samples;
+#ifdef HWPMC_DEBUG
+ counter_u64_t pe_samples_emitted;
+ counter_u64_t pe_samples_dropped;
+ volatile u_int pe_test_sample_pid;
+ volatile u_int pe_test_sample_tid;
+#endif
+ bool pe_is_leader;
+ struct pmc_op_pmcallocate pe_alloc;
+ pmc_sched_constraint_t pe_cons;
+ /*
+ * This field holds system-mode progress toward the next sample.
+ * The value survives eviction.
+ * For virtual mode, per-thread state is in pp_pmu_residuals instead.
+ * A value of zero means: start from the reload count.
+ */
+ pmc_value_t pe_residual;
+};
+
+/*
+ * This is the explicit residual state.
+ * A raw count of zero can mean overflow, or no saved progress. To
+ * remove this ambiguity, track the state in a separate field.
+ */
+enum pmu_residual_state {
+ PMU_RESIDUAL_UNINITIALIZED = 0, /* no saved progress; full reload */
+ PMU_RESIDUAL_COUNT_VALID = 1, /* ptr_value is a partial count */
+ PMU_RESIDUAL_OVERFLOW_PENDING = 2, /* overflow seen at schedule-out */
+};
+
+/*
+ * This struct holds saved sampling progress for one virtual thread.
+ * Hardware rows are not permanent, so use the event and the TID as the
+ * key. The process's pp_tdslock protects this data.
+ */
+struct pmu_thread_residual {
+ LIST_ENTRY(pmu_thread_residual) ptr_next;
+ pmu_event_t *ptr_event;
+ lwpid_t ptr_tid;
+ pmc_value_t ptr_value;
+ uint8_t ptr_state; /* pmu_residual_state */
+};
+
+/*
+ * This struct holds one authoritative group-target edge.
+ * The edge can be explicit or inherited.
+ * This struct does not store hardware-row links.
+ * To change this data, hold pmc_sx exclusive.
+ * If you also need a process-tree snapshot, take the locks in this
+ * order: pmc_sx, then proctree_lock, then pp_pmu_lock.
+ */
+struct pmu_group_target {
+ LIST_ENTRY(pmu_group_target) pgt_group_next; /* pg_targets */
+ LIST_ENTRY(pmu_group_target) pgt_process_next; /* pp_pmu_targets */
+ pmu_group_t *pgt_group;
+ struct pmc_process *pgt_pp;
+ bool pgt_explicit;
+};
+
+struct pmu_group_cpu_state {
+ LIST_ENTRY(pmu_group_cpu_state) pgcs_next;
+ pmu_group_t *pgcs_group;
+ struct thread *pgcs_td;
+ bool pgcs_counted;
+ bool pgcs_placed;
+ bool pgcs_transitioning;
+};
+
+struct pmu_group_time_snapshot {
+ uint64_t pgts_enabled;
+ uint64_t pgts_running;
+ uint64_t pgts_enabled_wall;
+ uint64_t pgts_wall;
+ bool pgts_system;
+};
+
+/*
+ * This is a PMU event group for atomic scheduling.
+ * All sibling events are assigned together, or deferred together.
+ */
+struct pmu_group {
+ LIST_ENTRY(pmu_group) pg_owner_next;
+ LIST_ENTRY(pmu_group) pg_proc_next;
+ uint32_t pg_id;
+ struct pmc_owner *pg_owner;
+ pmu_event_t *pg_leader;
+ TAILQ_HEAD(, pmu_event) pg_events;
+ u_int pg_nevents;
+ bool pg_committed;
+ bool pg_assigned; /* SCHEDULED if true */
+ bool pg_running; /* between start/stop */
+ bool pg_defer_ok; /* PMC_F_GROUP_MUX hint */
+ struct pmu_group_target *pg_explicit_target;
+ struct pmc_process *pg_pp; /* current scheduling anchor */
+ /*
+ * This is the authoritative target set.
+ * If pg_explicit_target is not NULL, it holds the user-requested
+ * edge from this list. pg_pp is only the current anchor.
+ */
+ LIST_HEAD(, pmu_group_target) pg_targets;
+ /* Virtual enabled and running times use thread ticks; wall uses TSC. */
+ uint64_t pg_time_enabled_ticks;
+ uint64_t pg_time_running_ticks;
+ uint64_t pg_enabled_wall_ticks;
+ uint64_t pg_wall_start_ticks;
+ uint64_t pg_wall_ticks;
+ uint64_t pg_timestamp_ticks;
+ uint64_t pg_tickrate;
+ struct mtx pg_snapshot_lock;
+ u_int pg_oncpu_threads;
+ u_int pg_running_threads;
+ struct pmu_group_cpu_state *pg_cpu_state;
+ u_int pg_ncpu;
+ bool pg_account_blocked;
+ bool pg_account_placement_admit;
+ bool pg_snapshot_pending;
+ bool pg_snapshot_active;
+ bool pg_releasing;
+ /*
+ * This is the system-mode (PMC_MODE_SC) state.
+ * A system group binds to one CPU and rotates on that CPU.
+ */
+ bool pg_system; /* system-wide group */
+ bool pg_sys_listed; /* on pmu_syscpu list */
+ bool pg_sscounted; /* in po_sscount */
+ int pg_cpu; /* bound CPU (system) */
+};
+
+/*
+ * These are the core hooks from hwpmc_mod.c.
+ */
+struct pmc_mdep *hwpmc_get_mdep(void);
+struct pmc_classdep *hwpmc_ri_to_classdep(int ri, int *adjri);
+bool hwpmc_can_allocate_row(int ri, enum pmc_mode mode);
+bool hwpmc_can_allocate_rowindex(struct proc *p, unsigned int ri, int cpu);
+void hwpmc_mark_row_thread(int ri);
+void hwpmc_unmark_row_thread(int ri);
+void hwpmc_mark_row_standalone(int ri);
+void hwpmc_unmark_row_standalone(int ri);
+bool hwpmc_row_is_unallocated(int cpu, int ri);
+void hwpmc_unconfigure_row_all_cpus(struct pmc *pm, int ri);
+
+/*
+ * Start and stop system-mode PMC hardware on the target CPU.
+ * The values accumulate across multiplex windows.
+ */
+int hwpmc_pmu_sys_start_row(int cpu, struct pmc *pm);
+void hwpmc_sscount_add(struct pmc_owner *po);
+void hwpmc_sscount_sub(struct pmc_owner *po);
+void hwpmc_pmu_sys_stop_row(int cpu, struct pmc *pm);
+
+/*
+ * This is the hardware row assigner (hwpmc_assign.c).
+ */
+int pmu_assign_group(pmu_group_t *pg, struct proc *p, int cpu);
+void pmu_unassign_group(pmu_group_t *pg, int cpu);
+int pmu_group_can_fit(pmu_group_t *pg);
+int pmu_group_can_place(pmu_group_t *pg, struct proc *p, int cpu);
+int pmu_group_satisfiable(pmu_group_t *pg, struct proc *p, int cpu,
+ uint64_t evictable_rows);
+int pmu_validate_group(pmu_group_t *pg);
+int pmu_event_get_constraint(pmu_event_t *pe,
+ pmc_sched_constraint_t *cons);
+bool pmu_class_supports_grouping(enum pmc_class class);
+
+/*
+ * This is the group lifecycle (hwpmc_pmu.c).
+ */
+void pmu_group_create(struct pmc_owner *po, uint32_t *pg_id);
+int pmu_group_add(pmu_group_t *pg, struct pmc *pm, bool leader);
+int pmu_group_commit(pmu_group_t *pg);
+pmu_group_t *pmu_group_lookup(struct pmc_owner *po, uint32_t pg_id);
+u_int pmu_group_prepare_release(pmu_group_t *pg, struct pmc **members,
+ u_int capacity, struct pmc_process **released_pp);
+void pmu_group_release(pmu_group_t *pg);
+void pmu_group_accounting_initialize(void);
+void pmu_group_accounting_finalize(void);
+void pmu_group_time_snapshot_locked(pmu_group_t *pg,
+ struct pmu_group_time_snapshot *snapshot, uint64_t now);
+void pmu_event_destroy(pmu_event_t *pe);
+pmu_event_t *pmu_event_from_pmc(struct pmc *pm);
+pmu_group_t *pmu_group_from_pmc(struct pmc *pm);
+u_int pmu_group_inherit(struct pmc_process *ppold, struct pmc_process *ppnew,
+ struct pmc **missed_leaders, u_int missed_capacity, u_int *nmissed);
+bool pmu_group_follows_fork(pmu_group_t *pg);
+void pmu_group_disinherit(struct pmc_process *pp);
+struct pmu_group_target *pmu_group_target_alloc(pmu_group_t *pg,
+ struct pmc_process *pp, bool explicit, int malloc_flags);
+void pmu_group_target_publish(struct pmu_group_target *pgt);
+void pmu_group_target_discard(struct pmu_group_target *pgt);
+void pmu_group_target_remove(struct pmu_group_target *pgt);
+void pmu_event_save_residual(struct pmc *pm, struct pmc_process *pp, int ri);
+void pmu_event_set_residual(struct pmc *pm, pmc_value_t residual);
+void pmu_event_restore_thread_residual(struct pmc *pm,
+ struct pmc_process *pp, int ri);
+pmc_value_t pmu_event_restore_residual(struct pmc *pm);
+void pmu_thread_residual_remove(struct pmc_process *pp, lwpid_t tid);
+#ifdef HWPMC_DEBUG
+int pmu_event_get_thread_residual(struct pmc *pm, struct pmc_process *pp,
+ lwpid_t tid, pmc_value_t *value, uint8_t *state);
+int pmu_event_set_thread_residual(struct pmc *pm, struct pmc_process *pp,
+ lwpid_t tid, pmc_value_t value, uint8_t state);
+#endif
+
+int pmu_group_on_allocate(struct pmc *pm, const struct pmc_op_pmcallocate *pa);
+int pmu_group_on_attach(struct pmc *pm, struct proc *p);
+int pmu_group_on_start(struct pmc *pm);
+void pmu_group_on_stop(struct pmc *pm);
+void pmu_group_on_release(struct pmc *pm);
+void pmu_group_csw_in(struct thread *td, struct pmc_process *pp);
+void pmu_group_csw_in_complete(struct thread *td, int cpu);
+bool pmu_group_csw_can_start(struct pmc *pm, struct thread *td, int cpu);
+void pmu_group_csw_out(struct thread *td, int cpu);
+void pmu_group_csw_out_complete(struct thread *td, int cpu);
+
+/*
+ * These are the system-mode (PMC_MODE_SC) group lifecycle functions.
+ */
+int pmu_sys_group_on_start(struct pmc *pm);
+void pmu_sys_group_on_stop(struct pmc *pm);
+void pmu_sys_group_pre_release(struct pmc *pm);
+
+/*
+ * These are the process descriptor PMU lifecycle functions.
+ * Before you call pmu_pp_destroy(), hold pmc_sx exclusive.
+ */
+void pmu_pp_init(struct pmc_process *pp);
+void pmu_pp_destroy(struct pmc_process *pp);
+void pmu_group_detach_target(pmu_group_t *pg, struct pmc_process *pp);
+void pmu_group_detach_inherited_target(pmu_group_t *pg, struct pmc_process *pp);
+void pmu_pp_kick_after_exec(struct pmc_process *pp);
+
+/*
+ * Find a process descriptor for the PMU layer.
+ */
+struct pmc_process *pmc_find_process_descriptor_pmu(struct proc *p,
+ uint32_t mode);
+
+void hwpmc_pmu_force_context_switch(void);
+
+/*
+ * These are the rotation helper functions (hwpmc_mod.c).
+ */
+void pmc_rotation_drain_set(struct pmc *pm, bool drain);
+void pmc_rotation_drain(struct pmc *pm);
+void pmc_rotation_drain_cpu(struct pmc *pm, int cpu);
+void pmc_rotation_detach(struct pmc *pm, struct pmc_process *pp);
+void pmc_rotation_attach(struct pmc *pm, struct pmc_process *pp);
+struct pmc_target *pmc_target_object_alloc(int malloc_flags);
+void pmc_target_object_free(struct pmc_target *pt);
+void pmc_link_target_process_preallocated(struct pmc *pm,
+ struct pmc_process *pp, struct pmc_target *pt);
+
+#endif /* _KERNEL */
+
+#endif /* _DEV_HWPMC_PMU_H_ */
diff --git a/sys/dev/hwpmc/hwpmc_pmu.c b/sys/dev/hwpmc/hwpmc_pmu.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/hwpmc/hwpmc_pmu.c
@@ -0,0 +1,2302 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * This file has PMU event grouping, time accounting, and multiplex
+ * rotation.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/kthread.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mutex.h>
+#include <sys/pcpu.h>
+#include <sys/pmc.h>
+#include <sys/pmckern.h>
+#include <sys/pmclog.h>
+#include <sys/prng.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/taskqueue.h>
+#include <sys/unistd.h>
+
+#include "hwpmc_pmu.h"
+
+static MALLOC_DEFINE(M_PMU, "pmu", "hwpmc PMU grouping");
+
+static uint32_t pmu_next_group_id = 1;
+
+extern struct mtx_pool *pmc_mtxpool;
+
+LIST_HEAD(pmu_group_cpu_list, pmu_group_cpu_state);
+static struct pmu_group_cpu_list pmu_group_cpu_active[MAXCPU];
+static LIST_HEAD(, pmu_thread_residual) pmu_residual_freelist;
+static struct mtx pmu_residual_freelist_mtx;
+static struct task pmu_residual_free_task;
+
+SYSCTL_DECL(_kern_hwpmc);
+
+/*
+ * This is the multiplex rotation period, in milliseconds.
+ */
+static int pmu_mux_period_ms = 50;
+SYSCTL_INT(_kern_hwpmc, OID_AUTO, mux_period_ms, CTLFLAG_RWTUN,
+ &pmu_mux_period_ms, 0,
+ "PMU multiplex rotation period floor in milliseconds");
+
+/* Return the rotation window in ticks, with random jitter. */
+static int
+pmu_rot_period_ticks(void)
+{
+ int period_ticks;
+
+ period_ticks = (pmu_mux_period_ms * hz) / 1000;
+ if (period_ticks < 1)
+ period_ticks = 1;
+ return (period_ticks + prng32_bounded(period_ticks / 4 + 1));
+}
+
+static void pmu_pp_rotate_thread(void *arg);
+static int pmu_pp_schedule_in(struct pmc_process *pp, pmu_group_t *pg);
+static void pmu_pp_schedule_out(struct pmc_process *pp, pmu_group_t *pg,
+ bool drain_samples);
+static void pmu_pp_kick_rotate(struct pmc_process *pp);
+static void pmu_pp_unlink_group(pmu_group_t *pg, struct pmc_process *pp);
+static void pmu_group_attach_siblings(pmu_group_t *pg,
+ struct pmc_process *pp);
+static void pmu_event_remove_target_residuals(pmu_event_t *pe,
+ struct pmc_process *pp);
+static int pmu_event_set_thread_residual_impl(struct pmc *pm,
+ struct pmc_process *pp, lwpid_t tid, pmc_value_t value, uint8_t state);
+static struct pmu_thread_residual *pmu_thread_residual_find_locked(
+ struct pmc_process *pp, pmu_event_t *pe, lwpid_t tid);
+static bool pmu_thread_exists_locked(struct pmc_process *pp, lwpid_t tid);
+static void pmu_thread_residual_defer_free(struct pmu_thread_residual *ptr);
+static void pmu_thread_residual_free_task_fn(void *arg, int pending);
+
+/*
+ * This struct holds state for system-mode (PMC_MODE_SC) multiplexing.
+ */
+struct pmu_syscpu {
+ struct pmu_group_list sc_groups; /* Groups bound to this CPU */
+ pmu_group_t *sc_cursor; /* Rotation cursor */
+ struct thread *sc_td; /* Rotation thread */
+ u_int sc_quiesce;
+ bool sc_running; /* Is the rotation thread active? */
+ bool sc_needed; /* Is a deferred group waiting? */
+};
+static struct pmu_syscpu pmu_syscpu[MAXCPU];
+
+
+void
+pmu_group_accounting_initialize(void)
+{
+ u_int cpu;
+
+ for (cpu = 0; cpu < MAXCPU; cpu++)
+ LIST_INIT(&pmu_group_cpu_active[cpu]);
+ LIST_INIT(&pmu_residual_freelist);
+ mtx_init(&pmu_residual_freelist_mtx, "pmu-residuals", "pmc-leaf",
+ MTX_SPIN);
+ TASK_INIT(&pmu_residual_free_task, 0, pmu_thread_residual_free_task_fn,
+ NULL);
+}
+
+void
+pmu_group_accounting_finalize(void)
+{
+ u_int cpu;
+
+ for (cpu = 0; cpu < MAXCPU; cpu++)
+ KASSERT(LIST_EMPTY(&pmu_group_cpu_active[cpu]),
+ ("[pmu] active accounting markers on CPU %u", cpu));
+ taskqueue_drain(taskqueue_fast, &pmu_residual_free_task);
+ pmu_thread_residual_free_task_fn(NULL, 0);
+ KASSERT(LIST_EMPTY(&pmu_residual_freelist),
+ ("[pmu] deferred residual frees remain"));
+ mtx_destroy(&pmu_residual_freelist_mtx);
+}
+
+static void
+pmu_group_time_update_locked(pmu_group_t *pg, uint64_t now)
+{
+ uint64_t delta;
+
+ if (pg->pg_timestamp_ticks == 0) {
+ pg->pg_timestamp_ticks = now;
+ return;
+ }
+ if (now <= pg->pg_timestamp_ticks)
+ return;
+
+ delta = now - pg->pg_timestamp_ticks;
+ if (pg->pg_running) {
+ if (pg->pg_system) {
+ if (pg->pg_cpu >= 0 && pmc_cpu_is_active(pg->pg_cpu)) {
+ pg->pg_time_enabled_ticks += delta;
+ pg->pg_enabled_wall_ticks += delta;
+ if (pg->pg_account_placement_admit)
+ pg->pg_time_running_ticks += delta;
+ }
+ } else {
+ pg->pg_time_enabled_ticks +=
+ (uint64_t)pg->pg_oncpu_threads * delta;
+ pg->pg_time_running_ticks +=
+ (uint64_t)pg->pg_running_threads * delta;
+ if (pg->pg_oncpu_threads != 0)
+ pg->pg_enabled_wall_ticks += delta;
+ }
+ }
+ pg->pg_timestamp_ticks = now;
+}
+
+static void
+pmu_group_running_start_locked(pmu_group_t *pg, uint64_t now)
+{
+
+ pmu_group_time_update_locked(pg, now);
+ if (!pg->pg_running) {
+ pg->pg_running = true;
+ pg->pg_wall_start_ticks = now;
+ }
+}
+
+static void
+pmu_group_running_stop_locked(pmu_group_t *pg, uint64_t now)
+{
+
+ pmu_group_time_update_locked(pg, now);
+ if (!pg->pg_running)
+ return;
+ if (now > pg->pg_wall_start_ticks)
+ pg->pg_wall_ticks += now - pg->pg_wall_start_ticks;
+ pg->pg_wall_start_ticks = 0;
+ pg->pg_running = false;
+}
+
+static uint64_t
+pmu_group_ticks_to_ns(uint64_t ticks, uint64_t tickrate)
+{
+ uint64_t frac, quot, rem, secs;
+
+ if (tickrate == 0)
+ return (0);
+
+ /*
+ * Scale ticks to nanoseconds without 128-bit division:
+ * (ticks / rate) * 1e9 + (ticks % rate) * 1e9 / rate.
+ */
+ quot = ticks / tickrate;
+ rem = ticks % tickrate;
+
+ if (quot > UINT64_MAX / 1000000000)
+ return (UINT64_MAX);
+ secs = quot * 1000000000;
+
+ /* Prevent overflow for high tick rates. */
+ if (rem <= UINT64_MAX / 1000000000)
+ frac = rem * 1000000000 / tickrate;
+ else
+ frac = rem / (tickrate / 1000000000);
+
+ if (secs > UINT64_MAX - frac)
+ return (UINT64_MAX);
+ return (secs + frac);
+}
+
+void
+pmu_group_time_snapshot_locked(pmu_group_t *pg,
+ struct pmu_group_time_snapshot *snapshot, uint64_t now)
+{
+ uint64_t enabled, enabled_wall, running, tickrate, wall;
+
+ KASSERT(pg != NULL && snapshot != NULL,
+ ("[pmu] invalid group time snapshot"));
+ pmu_group_time_update_locked(pg, now);
+ enabled = pg->pg_time_enabled_ticks;
+ running = MIN(pg->pg_time_running_ticks, enabled);
+ enabled_wall = pg->pg_enabled_wall_ticks;
+ wall = pg->pg_wall_ticks;
+ if (pg->pg_running && now > pg->pg_wall_start_ticks)
+ wall += now - pg->pg_wall_start_ticks;
+ tickrate = pg->pg_tickrate;
+ snapshot->pgts_system = pg->pg_system;
+
+ snapshot->pgts_enabled = pmu_group_ticks_to_ns(enabled, tickrate);
+ snapshot->pgts_running = pmu_group_ticks_to_ns(running, tickrate);
+ snapshot->pgts_enabled_wall = pmu_group_ticks_to_ns(enabled_wall,
+ tickrate);
+ snapshot->pgts_wall = pmu_group_ticks_to_ns(wall, tickrate);
+ if (snapshot->pgts_running > snapshot->pgts_enabled)
+ snapshot->pgts_running = snapshot->pgts_enabled;
+}
+
+static void
+pmu_group_accounting_block_locked(pmu_group_t *pg, uint64_t now)
+{
+
+ pmu_group_time_update_locked(pg, now);
+ pg->pg_account_blocked = true;
+ pg->pg_account_placement_admit = false;
+}
+
+static void
+pmu_group_accounting_block(pmu_group_t *pg)
+{
+
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ pmu_group_accounting_block_locked(pg, cpu_ticks());
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+}
+
+static void
+pmu_group_accounting_drain(pmu_group_t *pg, bool release)
+{
+ struct pmc_binding pb;
+ u_int cpu;
+#ifdef INVARIANTS
+ int maxloop;
+
+ maxloop = 100 * MAX(1, pg->pg_ncpu);
+#endif
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pg->pg_account_blocked,
+ ("[pmu] draining unblocked group %u", pg->pg_id));
+
+ if (pg->pg_ncpu != 0)
+ pmc_save_cpu_binding(&pb);
+ for (;;) {
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ for (cpu = 0; cpu < pg->pg_ncpu; cpu++) {
+ if (pg->pg_cpu_state[cpu].pgcs_counted)
+ break;
+ }
+ if (cpu == pg->pg_ncpu) {
+ KASSERT(pg->pg_oncpu_threads == 0,
+ ("[pmu] group %u has count without marker",
+ pg->pg_id));
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ break;
+ }
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+#ifdef INVARIANTS
+ KASSERT(maxloop-- > 0,
+ ("[pmu] group %u accounting drain stuck", pg->pg_id));
+#endif
+ KASSERT(pmc_cpu_is_active(cpu),
+ ("[pmu] group %u marker on inactive CPU %u", pg->pg_id,
+ cpu));
+ pmc_select_cpu(cpu);
+ hwpmc_pmu_force_context_switch();
+ }
+ if (pg->pg_ncpu != 0)
+ pmc_restore_cpu_binding(&pb);
+
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ pmu_group_running_stop_locked(pg, cpu_ticks());
+ if (!release)
+ pg->pg_account_blocked = false;
+ KASSERT(pg->pg_running_threads == 0,
+ ("[pmu] group %u has running accounting threads", pg->pg_id));
+ for (cpu = 0; cpu < pg->pg_ncpu; cpu++) {
+ KASSERT(!pg->pg_cpu_state[cpu].pgcs_counted &&
+ !pg->pg_cpu_state[cpu].pgcs_placed &&
+ !pg->pg_cpu_state[cpu].pgcs_transitioning &&
+ pg->pg_cpu_state[cpu].pgcs_td == NULL,
+ ("[pmu] group %u has active CPU %u marker", pg->pg_id,
+ cpu));
+ }
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+}
+
+static void
+pmu_group_kick_placement(pmu_group_t *pg)
+{
+ struct pmc_binding pb;
+ bool kick;
+ u_int cpu;
+
+ KASSERT(!pg->pg_system && pg->pg_ncpu != 0,
+ ("[pmu] kick_placement: bad group %u", pg->pg_id));
+ pmc_save_cpu_binding(&pb);
+ for (cpu = 0; cpu < pg->pg_ncpu; cpu++) {
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ kick = pg->pg_cpu_state[cpu].pgcs_counted;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ if (!kick || !pmc_cpu_is_active(cpu))
+ continue;
+ pmc_select_cpu(cpu);
+ hwpmc_pmu_force_context_switch();
+ }
+ pmc_restore_cpu_binding(&pb);
+}
+
+static void
+pmu_group_accounting_prepare_release(pmu_group_t *pg)
+{
+ struct pmc_process *pp;
+
+ pp = pg->pg_pp;
+ if (pp != NULL)
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ pmu_group_accounting_block(pg);
+ if (pp != NULL)
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ pmu_group_accounting_drain(pg, true);
+}
+
+static void
+pmu_pp_stop_rotate(struct pmc_process *pp, const char *wmesg)
+{
+
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ pp->pp_pmu_rot_quiesce++;
+ pp->pp_pmu_rot_running = false;
+ pp->pp_pmu_rot_needed = false;
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ wakeup(&pp->pp_pmu_rot_needed);
+ while (pp->pp_pmu_rot_td != NULL)
+ (void)sx_sleep(&pp->pp_pmu_rot_td, &pmc_sx, 0, wmesg, 1);
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ KASSERT(pp->pp_pmu_rot_quiesce > 0,
+ ("[pmu] rotation quiesce underflow"));
+ pp->pp_pmu_rot_quiesce--;
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+}
+
+/* Add the group to the process list if it is not already there. */
+static void
+pmu_pp_link_group(pmu_group_t *pg, struct pmc_process *pp)
+{
+
+ if (pg->pg_pp != NULL)
+ return;
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ LIST_INSERT_HEAD(&pp->pp_pmu_groups, pg, pg_proc_next);
+ pg->pg_pp = pp;
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+}
+
+/*
+ * Does this group follow its targets through fork? This flag is
+ * leader-only and governs the whole group. Commit already rejects
+ * this flag on a system group.
+ */
+bool
+pmu_group_follows_fork(pmu_group_t *pg)
+{
+
+ return (pg->pg_committed && !pg->pg_releasing && !pg->pg_system &&
+ pg->pg_leader != NULL &&
+ (pg->pg_leader->pe_alloc.pm_flags & PMC_F_DESCENDANTS) != 0);
+}
+
+/* Find the authoritative edge for pp, if this group follows it already. */
+static struct pmu_group_target *
+pmu_group_find_target(pmu_group_t *pg, struct pmc_process *pp)
+{
+ struct pmu_group_target *pgt;
+
+ LIST_FOREACH(pgt, &pg->pg_targets, pgt_group_next) {
+ if (pgt->pgt_pp == pp)
+ return (pgt);
+ }
+ return (NULL);
+}
+
+/*
+ * This function selects a surviving target for a scheduling-anchor handoff.
+ * Prefer the explicit edge if it exists. If not, use any inherited edge.
+ * To call this function, hold pmc_sx exclusive.
+ */
+static struct pmu_group_target *
+pmu_group_replacement_target(pmu_group_t *pg, struct pmc_process *old_pp)
+{
+ struct pmu_group_target *pgt, *replacement;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ replacement = NULL;
+ LIST_FOREACH(pgt, &pg->pg_targets, pgt_group_next) {
+ if (pgt->pgt_pp == old_pp)
+ continue;
+ if (pgt->pgt_explicit)
+ return (pgt);
+ if (replacement == NULL)
+ replacement = pgt;
+ }
+ return (replacement);
+}
+
+static bool
+pmu_group_has_target(pmu_group_t *pg, struct pmc_process *pp)
+{
+
+ return (pmu_group_find_target(pg, pp) != NULL);
+}
+
+/*
+ * This function allocates a target edge. It does not publish it.
+ * Publication is a separate step. This lets a multi-process attach
+ * finish every fallible check before any edge becomes visible.
+ */
+struct pmu_group_target *
+pmu_group_target_alloc(pmu_group_t *pg, struct pmc_process *pp, bool explicit,
+ int malloc_flags)
+{
+ struct pmu_group_target *pgt;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pg != NULL && pp != NULL,
+ ("[pmu] invalid target allocation pg=%p pp=%p", pg, pp));
+ pgt = malloc(sizeof(*pgt), M_PMU, malloc_flags | M_ZERO);
+ if (pgt == NULL)
+ return (NULL);
+ pgt->pgt_pp = pp;
+ pgt->pgt_group = pg;
+ pgt->pgt_explicit = explicit;
+ return (pgt);
+}
+
+void
+pmu_group_target_publish(struct pmu_group_target *pgt)
+{
+ pmu_group_t *pg;
+ struct pmc_process *pp;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pgt != NULL && pgt->pgt_group != NULL && pgt->pgt_pp != NULL,
+ ("[pmu] invalid target publication pgt=%p", pgt));
+ pg = pgt->pgt_group;
+ pp = pgt->pgt_pp;
+ KASSERT(!pmu_group_has_target(pg, pp),
+ ("[pmu] duplicate group %u target pp=%p", pg->pg_id, pp));
+
+ LIST_INSERT_HEAD(&pg->pg_targets, pgt, pgt_group_next);
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ LIST_INSERT_HEAD(&pp->pp_pmu_targets, pgt, pgt_process_next);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ if (pgt->pgt_explicit) {
+ KASSERT(pg->pg_explicit_target == NULL && pg->pg_pp == NULL,
+ ("[pmu] group %u already has an explicit target", pg->pg_id));
+ pg->pg_explicit_target = pgt;
+ pmu_pp_link_group(pg, pp);
+ }
+}
+
+void
+pmu_group_target_discard(struct pmu_group_target *pgt)
+{
+
+ if (pgt == NULL)
+ return;
+ free(pgt, M_PMU);
+}
+
+void
+pmu_group_target_remove(struct pmu_group_target *pgt)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ struct pmc_process *pp;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pgt != NULL && pgt->pgt_group != NULL && pgt->pgt_pp != NULL,
+ ("[pmu] invalid target removal pgt=%p", pgt));
+ pg = pgt->pgt_group;
+ pp = pgt->pgt_pp;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pmu_event_remove_target_residuals(pe, pp);
+ if (pg->pg_pp == pp)
+ pmu_pp_unlink_group(pg, pp);
+ if (pg->pg_explicit_target == pgt)
+ pg->pg_explicit_target = NULL;
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ LIST_REMOVE(pgt, pgt_process_next);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ LIST_REMOVE(pgt, pgt_group_next);
+ pmu_group_target_discard(pgt);
+}
+
+/*
+ * This function records a target reached through fork.
+ * The allocation must not sleep. A fork must not fail.
+ * If the allocation fails, the group does not monitor the child.
+ * The caller then records the miss.
+ */
+static bool
+pmu_group_add_inherited(pmu_group_t *pg, struct pmc_process *pp)
+{
+ pmu_event_t *pe;
+ struct pmc_target *links[PMC_GROUP_MAX_MEMBERS];
+ struct pmu_group_target *pgt;
+ struct pmc *pm;
+ u_int i, nlinks;
+ int ri;
+
+ if (pmu_group_has_target(pg, pp))
+ return (true);
+ KASSERT(pg->pg_nevents <= PMC_GROUP_MAX_MEMBERS,
+ ("[pmu] group %u has too many members", pg->pg_id));
+ memset(links, 0, sizeof(links));
+ nlinks = 0;
+ pgt = pmu_group_target_alloc(pg, pp, false, M_NOWAIT);
+ if (pgt == NULL)
+ return (false);
+
+ if (pg->pg_assigned) {
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pm = pe->pe_pmc;
+ KASSERT(!PMC_ROW_IS_UNASSIGNED(pm),
+ ("[pmu] assigned group %u has unassigned member",
+ pg->pg_id));
+ ri = PMC_TO_ROWINDEX(pm);
+ if (pp->pp_pmcs[ri].pp_pmc != NULL)
+ goto fail;
+ links[nlinks] =
+ pmc_target_object_alloc(M_NOWAIT);
+ if (links[nlinks] == NULL)
+ goto fail;
+ nlinks++;
+ }
+
+ i = 0;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pmc_link_target_process_preallocated(pe->pe_pmc, pp,
+ links[i]);
+ links[i++] = NULL;
+ }
+ }
+
+ /*
+ * This is the linearization point. Every fallible allocation and
+ * every current row link is complete before the authoritative
+ * target becomes visible.
+ */
+ pmu_group_target_publish(pgt);
+ return (true);
+
+fail:
+ for (i = 0; i < nlinks; i++)
+ pmc_target_object_free(links[i]);
+ pmu_group_target_discard(pgt);
+ return (false);
+}
+
+/*
+ * Attach every group the parent is a target of to the child (spec §3.9).
+ * A group is taken all at once, or not at all, so that §2.4 holds in the
+ * child. If a group cannot be recorded, skip it whole and count it.
+ * The caller reports how many were missed.
+ *
+ * Only groups whose leader carries PMC_F_DESCENDANTS follow a fork, and
+ * only virtual ones. A system group is bound to a CPU and has no process
+ * target, so commit already rejects it.
+ */
+u_int
+pmu_group_inherit(struct pmc_process *ppold, struct pmc_process *ppnew,
+ struct pmc **missed_leaders, u_int missed_capacity, u_int *nmissed)
+{
+ pmu_group_t *pg;
+ struct pmu_group_target *pgt;
+ u_int ninherited, nmiss;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ ninherited = 0;
+ nmiss = 0;
+
+ /* Direct parents and inherited parents share one target list. */
+ LIST_FOREACH(pgt, &ppold->pp_pmu_targets, pgt_process_next) {
+ pg = pgt->pgt_group;
+ if (!pmu_group_follows_fork(pg))
+ continue;
+ if (pmu_group_add_inherited(pg, ppnew)) {
+ ninherited++;
+ continue;
+ }
+ /* Record the missed group by its leader handle, for per-group logs. */
+ if (missed_leaders != NULL && nmiss < missed_capacity &&
+ pg->pg_leader != NULL)
+ missed_leaders[nmiss] = pg->pg_leader->pe_pmc;
+ nmiss++;
+ }
+
+ if (nmissed != NULL)
+ *nmissed = nmiss;
+ return (ninherited);
+}
+
+/* Remove every group-target edge held by pp. This includes both explicit and inherited edges. */
+void
+pmu_group_disinherit(struct pmc_process *pp)
+{
+ struct pmu_group_target *pgt;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ while ((pgt = LIST_FIRST(&pp->pp_pmu_targets)) != NULL)
+ pmu_group_target_remove(pgt);
+}
+
+/* Remove the group from the process list and update the cursor. */
+static void
+pmu_pp_unlink_group(pmu_group_t *pg, struct pmc_process *pp)
+{
+
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ if (pp->pp_pmu_rot_cursor == pg)
+ pp->pp_pmu_rot_cursor = LIST_NEXT(pg, pg_proc_next);
+ LIST_REMOVE(pg, pg_proc_next);
+ pg->pg_pp = NULL;
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+}
+
+/* Assign deferred groups and restart rotation. */
+static void
+pmu_pp_backfill(struct pmc_process *pp)
+{
+ pmu_group_t *pg;
+
+ if (pp->pp_pmu_unhashed)
+ return;
+ LIST_FOREACH(pg, &pp->pp_pmu_groups, pg_proc_next) {
+ if (pg->pg_running && !pg->pg_assigned)
+ (void)pmu_pp_schedule_in(pp, pg);
+ }
+ pmu_pp_kick_rotate(pp);
+}
+
+pmu_event_t *
+pmu_event_from_pmc(struct pmc *pm)
+{
+ return (pm != NULL ? pm->pm_pmu : NULL);
+}
+
+/* Return the group for the PMC, or NULL. */
+pmu_group_t *
+pmu_group_from_pmc(struct pmc *pm)
+{
+ pmu_event_t *pe;
+
+ pe = pmu_event_from_pmc(pm);
+ return (pe != NULL ? pe->pe_group : NULL);
+}
+
+void
+pmu_event_destroy(pmu_event_t *pe)
+{
+ if (pe == NULL)
+ return;
+ counter_u64_free(pe->pe_samples);
+#ifdef HWPMC_DEBUG
+ counter_u64_free(pe->pe_samples_emitted);
+ counter_u64_free(pe->pe_samples_dropped);
+#endif
+ free(pe, M_PMU);
+}
+
+void
+pmu_group_create(struct pmc_owner *po, uint32_t *pg_id)
+{
+ pmu_group_t *pg;
+
+ KASSERT(po != NULL && pg_id != NULL,
+ ("[pmu] pmu_group_create: null po=%p pg_id=%p", po, pg_id));
+
+ pg = malloc(sizeof(*pg), M_PMU, M_WAITOK | M_ZERO);
+ mtx_init(&pg->pg_snapshot_lock, "pmu group snapshot", NULL, MTX_SPIN);
+ pg->pg_id = pmu_next_group_id++;
+ pg->pg_owner = po;
+ TAILQ_INIT(&pg->pg_events);
+ LIST_INIT(&pg->pg_targets);
+ LIST_INSERT_HEAD(&po->po_groups, pg, pg_owner_next);
+ *pg_id = pg->pg_id;
+}
+
+pmu_group_t *
+pmu_group_lookup(struct pmc_owner *po, uint32_t pg_id)
+{
+ pmu_group_t *pg;
+
+ LIST_FOREACH(pg, &po->po_groups, pg_owner_next) {
+ if (pg->pg_id == pg_id && !pg->pg_releasing)
+ return (pg);
+ }
+ return (NULL);
+}
+
+static void
+pmu_group_accounting_init(pmu_group_t *pg)
+{
+ u_int cpu;
+
+ if (pg->pg_tickrate != 0)
+ return;
+ pg->pg_tickrate = cpu_tickrate();
+ KASSERT(pg->pg_tickrate != 0, ("[pmu] zero CPU tick rate"));
+ if (PMC_IS_SYSTEM_MODE(pg->pg_leader->pe_alloc.pm_mode))
+ return;
+ pg->pg_ncpu = pmc_cpu_max();
+ pg->pg_cpu_state = malloc(sizeof(*pg->pg_cpu_state) * pg->pg_ncpu,
+ M_PMU, M_WAITOK | M_ZERO);
+ for (cpu = 0; cpu < pg->pg_ncpu; cpu++)
+ pg->pg_cpu_state[cpu].pgcs_group = pg;
+}
+
+int
+pmu_group_add(pmu_group_t *pg, struct pmc *pm, bool leader)
+{
+ pmu_event_t *pe;
+
+ KASSERT(pg != NULL && pm != NULL,
+ ("[pmu] pmu_group_add: null pg=%p pm=%p", pg, pm));
+
+ if (pg->pg_committed) {
+ PMCDBG1(PMC, OPS, 1, "group_add: gid=%u already committed",
+ pg->pg_id);
+ return (EBUSY);
+ }
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL) {
+ PMCDBG2(PMC, OPS, 1,
+ "group_add: pm_pmu==NULL pm=%p flags=0x%x",
+ pm, pm->pm_flags);
+ return (EINVAL);
+ }
+ if (!PMC_ROW_IS_UNASSIGNED(pe->pe_pmc)) {
+ PMCDBG2(PMC, OPS, 1,
+ "group_add: already assigned pm=%p id=0x%jx",
+ pm, (uintmax_t)pm->pm_id);
+ return (EBUSY);
+ }
+ if (pe->pe_group == pg)
+ return (leader ? EINVAL : 0);
+ if (pe->pe_group != NULL) {
+ PMCDBG3(PMC, OPS, 1,
+ "group_add: pe %p in gid=%u, requested gid=%u",
+ pe, pe->pe_group->pg_id, pg->pg_id);
+ return (EBUSY);
+ }
+ if (pg->pg_nevents >= PMC_GROUP_MAX_MEMBERS)
+ return (E2BIG);
+ if (leader && pg->pg_leader != NULL)
+ return (EINVAL);
+
+ pe->pe_group = pg;
+ pe->pe_is_leader = leader;
+ TAILQ_INSERT_TAIL(&pg->pg_events, pe, pe_sibling);
+ pg->pg_nevents++;
+
+ if (leader)
+ pg->pg_leader = pe;
+
+ return (0);
+}
+
+int
+pmu_group_commit(pmu_group_t *pg)
+{
+ pmu_event_t *pe;
+ struct pmc_mdep *mdep;
+ struct pmc_owner *po;
+ struct proc *p;
+ int error;
+
+ if (pg == NULL)
+ return (EINVAL);
+ if (pg->pg_committed)
+ return (EBUSY);
+ mdep = hwpmc_get_mdep();
+ if (mdep == NULL)
+ return (EOPNOTSUPP);
+ if (pg->pg_nevents > (u_int)mdep->pmd_npmc)
+ return (ENOSPC);
+
+ error = pmu_validate_group(pg);
+ if (error != 0) {
+ PMCDBG3(PMC, OPS, 1,
+ "group_commit: validate gid=%u err=%d nevents=%u",
+ pg->pg_id, error, pg->pg_nevents);
+ return (error);
+ }
+
+ /* Cache the event constraints, for sorting. */
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ error = pmu_event_get_constraint(pe, &pe->pe_cons);
+ if (error != 0) {
+ PMCDBG2(PMC, OPS, 1,
+ "group_commit: constraint err=%d class=%d",
+ error, (int)pe->pe_alloc.pm_class);
+ return (error);
+ }
+ }
+
+ pg->pg_defer_ok = (pg->pg_leader->pe_alloc.pm_flags &
+ PMC_F_GROUP_MUX) != 0;
+
+ error = pmu_group_can_fit(pg);
+ if (error != 0)
+ return (error);
+ pmu_group_accounting_init(pg);
+
+ po = pg->pg_owner;
+ p = po != NULL ? po->po_owner : NULL;
+ if (p == NULL)
+ return (EINVAL);
+
+ /*
+ * A system group binds to one CPU.
+ * Hardware assignment happens when the group starts.
+ */
+ if (PMC_IS_SYSTEM_MODE(pg->pg_leader->pe_alloc.pm_mode)) {
+ pg->pg_system = true;
+ pg->pg_cpu = pg->pg_leader->pe_alloc.pm_cpu;
+
+ /* The commit succeeds if the group can fit on the PMU. */
+ error = pmu_group_can_place(pg, p, pg->pg_cpu);
+ PMCDBG5(PMC, OPS, 1,
+ "group_commit: SYS gid=%u cpu=%d nevents=%u "
+ "place=%d defer_ok=%d", pg->pg_id, pg->pg_cpu,
+ pg->pg_nevents, error, (int)pg->pg_defer_ok);
+ if (error != 0 && error != ENOSPC)
+ return (error);
+
+ pg->pg_committed = true;
+ return (0);
+ }
+
+ /*
+ * Assign hardware rows now, if rows are free.
+ * If rows are busy and PMC_F_GROUP_MUX is set, defer the assignment.
+ */
+ error = pmu_group_can_place(pg, p, PMC_CPU_ANY);
+ if (error == 0)
+ error = pmu_assign_group(pg, p, PMC_CPU_ANY);
+ if (error == 0) {
+ PMCDBG1(PMC, OPS, 1,
+ "group_commit: gid=%u BOUND", pg->pg_id);
+ } else if (error != EBUSY && error != ENOSPC) {
+ return (error);
+ } else if (!pg->pg_defer_ok) {
+ return (ENOSPC);
+ } else {
+ PMCDBG1(PMC, OPS, 1,
+ "group_commit: gid=%u DEFERRED", pg->pg_id);
+ }
+
+ pg->pg_committed = true;
+ return (0);
+}
+
+u_int
+pmu_group_prepare_release(pmu_group_t *pg, struct pmc **members,
+ u_int capacity, struct pmc_process **released_pp)
+{
+ pmu_event_t *pe, *tmp;
+ struct pmu_group_target *pgt;
+ struct pmc_process *held_pp, *pp;
+ bool pp_unhashed;
+ u_int n;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pg != NULL && members != NULL,
+ ("[pmu] invalid group release preparation"));
+ KASSERT(capacity >= pg->pg_nevents,
+ ("[pmu] group release capacity %u < %u", capacity,
+ pg->pg_nevents));
+ KASSERT(!pg->pg_releasing,
+ ("[pmu] group %u release already active", pg->pg_id));
+
+ pg->pg_releasing = true;
+ if (released_pp != NULL)
+ *released_pp = NULL;
+
+ if (pg->pg_system) {
+ if (pg->pg_committed && !pg->pg_account_blocked)
+ pmu_group_accounting_prepare_release(pg);
+ } else {
+ if (pg->pg_committed && !pg->pg_account_blocked)
+ pmu_group_accounting_prepare_release(pg);
+ held_pp = pg->pg_pp;
+ if (held_pp != NULL) {
+ mtx_lock_spin(&held_pp->pp_pmu_lock);
+ held_pp->pp_pmu_refs++;
+ mtx_unlock_spin(&held_pp->pp_pmu_lock);
+ pmu_pp_stop_rotate(held_pp, "muxrel");
+ }
+
+ pp = pg->pg_pp;
+ if (pg->pg_assigned) {
+ if (pp != NULL)
+ pmu_pp_schedule_out(pp, pg, false);
+ else
+ pmu_unassign_group(pg, 0);
+ }
+ if (pg->pg_pp != NULL) {
+ pp = pg->pg_pp;
+ pmu_pp_unlink_group(pg, pp);
+ pmu_pp_backfill(pp);
+ }
+ if (held_pp != NULL) {
+ mtx_lock_spin(&held_pp->pp_pmu_lock);
+ KASSERT(held_pp->pp_pmu_refs > 0,
+ ("[pmu] process reference underflow"));
+ held_pp->pp_pmu_refs--;
+ pp_unhashed = held_pp->pp_pmu_unhashed;
+ mtx_unlock_spin(&held_pp->pp_pmu_lock);
+ wakeup(&held_pp->pp_pmu_refs);
+ if (released_pp != NULL && !pp_unhashed && pp == held_pp)
+ *released_pp = held_pp;
+ }
+ }
+
+ KASSERT(!pg->pg_assigned && pg->pg_pp == NULL &&
+ !pg->pg_sys_listed,
+ ("[pmu] group %u still scheduled during release", pg->pg_id));
+ n = 0;
+ TAILQ_FOREACH_SAFE(pe, &pg->pg_events, pe_sibling, tmp) {
+ KASSERT(n < capacity,
+ ("[pmu] group %u release member overflow", pg->pg_id));
+ KASSERT(PMC_ROW_IS_UNASSIGNED(pe->pe_pmc),
+ ("[pmu] group %u member still assigned", pg->pg_id));
+ LIST_FOREACH(pgt, &pg->pg_targets, pgt_group_next)
+ pmu_event_remove_target_residuals(pe, pgt->pgt_pp);
+ members[n++] = pe->pe_pmc;
+ TAILQ_REMOVE(&pg->pg_events, pe, pe_sibling);
+ pe->pe_group = NULL;
+ pe->pe_is_leader = false;
+ }
+ pg->pg_leader = NULL;
+ pg->pg_nevents = 0;
+ if (pg->pg_owner != NULL) {
+ LIST_REMOVE(pg, pg_owner_next);
+ pg->pg_owner = NULL;
+ }
+ return (n);
+}
+
+void
+pmu_group_release(pmu_group_t *pg)
+{
+ u_int cpu;
+
+ if (pg == NULL)
+ return;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(LIST_EMPTY(&pg->pg_targets),
+ ("[pmu] freeing group %u with live targets", pg->pg_id));
+ KASSERT(pg->pg_explicit_target == NULL,
+ ("[pmu] freeing group %u with explicit target", pg->pg_id));
+ KASSERT(TAILQ_EMPTY(&pg->pg_events) && pg->pg_nevents == 0 &&
+ pg->pg_leader == NULL,
+ ("[pmu] freeing non-empty group %u", pg->pg_id));
+ KASSERT(!pg->pg_assigned && pg->pg_pp == NULL &&
+ !pg->pg_sys_listed && !pg->pg_sscounted,
+ ("[pmu] freeing scheduled group %u", pg->pg_id));
+ if (pg->pg_owner != NULL) {
+ LIST_REMOVE(pg, pg_owner_next);
+ pg->pg_owner = NULL;
+ }
+ KASSERT(pg->pg_oncpu_threads == 0 && pg->pg_running_threads == 0,
+ ("[pmu] freeing group with active accounting"));
+ KASSERT(!pg->pg_snapshot_pending && !pg->pg_snapshot_active,
+ ("[pmu] freeing group with active snapshot"));
+ for (cpu = 0; cpu < pg->pg_ncpu; cpu++) {
+ KASSERT(!pg->pg_cpu_state[cpu].pgcs_counted &&
+ !pg->pg_cpu_state[cpu].pgcs_placed &&
+ !pg->pg_cpu_state[cpu].pgcs_transitioning &&
+ pg->pg_cpu_state[cpu].pgcs_td == NULL,
+ ("[pmu] freeing group with active CPU marker"));
+ }
+ free(pg->pg_cpu_state, M_PMU);
+ mtx_destroy(&pg->pg_snapshot_lock);
+ free(pg, M_PMU);
+}
+
+int
+pmu_group_on_allocate(struct pmc *pm, const struct pmc_op_pmcallocate *pa)
+{
+ pmu_event_t *pe;
+
+ pe = malloc(sizeof(*pe), M_PMU, M_WAITOK | M_ZERO);
+ pe->pe_pmc = pm;
+ pe->pe_samples = counter_u64_alloc(M_WAITOK);
+#ifdef HWPMC_DEBUG
+ pe->pe_samples_emitted = counter_u64_alloc(M_WAITOK);
+ pe->pe_samples_dropped = counter_u64_alloc(M_WAITOK);
+#endif
+ pe->pe_alloc = *pa;
+ pm->pm_pmu = pe;
+ return (0);
+}
+
+int
+pmu_group_on_attach(struct pmc *pm, struct proc *p)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ struct pmu_group_target *pgt;
+ struct pmc_process *pp;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || pe->pe_group == NULL)
+ return (0);
+ pg = pe->pe_group;
+ if (pg->pg_explicit_target != NULL &&
+ pg->pg_explicit_target->pgt_pp->pp_proc != p)
+ return (EBUSY);
+ pp = pmc_find_process_descriptor_pmu(p, PMC_FLAG_ALLOCATE);
+ if (pp == NULL)
+ return (ENOMEM);
+ if (pg->pg_pp != NULL && pg->pg_pp != pp)
+ return (EBUSY);
+ if (pmu_group_has_target(pg, pp))
+ return (0);
+ pgt = pmu_group_target_alloc(pg, pp, true, M_WAITOK);
+ if (pgt == NULL)
+ return (ENOMEM);
+ pmu_group_target_publish(pgt);
+ return (0);
+}
+
+/*
+ * Find the target process for a group.
+ */
+static struct proc *
+pmu_group_target_proc(pmu_group_t *pg)
+{
+ if (pg == NULL || pg->pg_pp == NULL)
+ return (NULL);
+ return (pg->pg_pp->pp_proc);
+}
+
+int
+pmu_group_on_start(struct pmc *pm)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ struct pmc_process *pp;
+ struct proc *p;
+ int error;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || pe->pe_group == NULL)
+ return (0);
+
+ pg = pe->pe_group;
+ if (!pg->pg_committed)
+ return (EDOOFUS);
+
+ p = pmu_group_target_proc(pg);
+ if (p == NULL) {
+ PMCDBG2(PMC, OPS, 1,
+ "on_start: gid=%u pm=%p no target proc -> EINVAL",
+ pg->pg_id, pm);
+ return (EINVAL);
+ }
+
+ pp = pg->pg_pp;
+ if (pp != NULL && pp->pp_pmu_unhashed)
+ return (ESRCH);
+ if (pp == NULL)
+ pp = pmc_find_process_descriptor_pmu(p, PMC_FLAG_ALLOCATE);
+ if (pp == NULL)
+ return (ENOMEM);
+
+ pmu_pp_link_group(pg, pp);
+
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (pg->pg_account_blocked) {
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ return (EBUSY);
+ }
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+
+ if (pg->pg_assigned) {
+ PMCDBG3(PMC, OPS, 2,
+ "on_start: gid=%u pm=%p ASSIGNED, attaching siblings "
+ "on pp=%p", pg->pg_id, pm, pp);
+ pmu_group_attach_siblings(pg, pp);
+ } else {
+ error = pmu_pp_schedule_in(pp, pg);
+ PMCDBG4(PMC, OPS, 2,
+ "on_start: gid=%u pm=%p DEFERRED schedule_in=%d pp=%p",
+ pg->pg_id, pm, error, pp);
+ if (error != 0 && error != ENOSPC)
+ return (error);
+ }
+
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pe->pe_pmc->pm_state = PMC_STATE_RUNNING;
+ PMCDBG3(PMC, OPS, 3,
+ "on_start: gid=%u pm=%p ri=%d -> RUNNING",
+ pg->pg_id, pe->pe_pmc, PMC_TO_ROWINDEX(pe->pe_pmc));
+ }
+
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ pmu_group_running_start_locked(pg, cpu_ticks());
+ pg->pg_account_placement_admit = pg->pg_assigned;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+
+ pmu_pp_kick_rotate(pp);
+ return (0);
+}
+
+/*
+ * Mark the group stopped. Assigned rows stay reserved until release.
+ */
+void
+pmu_group_on_stop(struct pmc *pm)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ struct pmc_process *pp;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || pe->pe_group == NULL)
+ return;
+ /* Require exclusive lock ownership for grouped stops. */
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pg = pe->pe_group;
+ pp = pg->pg_pp;
+ if (pp != NULL)
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (!pg->pg_running) {
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ if (pp != NULL)
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ return;
+ }
+ pmu_group_accounting_block_locked(pg, cpu_ticks());
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ if (pp != NULL)
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_STOPPED;
+ pmu_group_accounting_drain(pg, false);
+}
+
+/* Mark one group on-CPU for the thread that is switching in. */
+static void
+pmu_group_csw_in_one(pmu_group_t *pg, struct thread *td, int cpu,
+ uint64_t now)
+{
+ struct pmu_group_cpu_state *pgcs;
+
+ if (pg->pg_system || cpu < 0 || (u_int)cpu >= pg->pg_ncpu)
+ return;
+ pgcs = &pg->pg_cpu_state[cpu];
+ /* Do not wait for a snapshot during a context switch. */
+ mtx_lock_spin(&pg->pg_snapshot_lock);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (!pg->pg_committed || !pg->pg_running || pg->pg_account_blocked) {
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+ return;
+ }
+ KASSERT(!pgcs->pgcs_counted,
+ ("[pmu] duplicate CPU %d marker for group %u", cpu, pg->pg_id));
+ pmu_group_time_update_locked(pg, now);
+ pgcs->pgcs_td = td;
+ pgcs->pgcs_counted = true;
+ pgcs->pgcs_placed = false;
+ pgcs->pgcs_transitioning = true;
+ pg->pg_oncpu_threads++;
+ LIST_INSERT_HEAD(&pmu_group_cpu_active[cpu], pgcs, pgcs_next);
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+}
+
+void
+pmu_group_csw_in(struct thread *td, struct pmc_process *pp)
+{
+ struct pmu_group_target *pgt;
+ uint64_t now;
+ int cpu;
+
+ /* Check the lists without a lock. */
+ if (LIST_EMPTY(&pp->pp_pmu_targets))
+ return;
+ cpu = PCPU_GET(cpuid);
+ now = cpu_ticks();
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ LIST_FOREACH(pgt, &pp->pp_pmu_targets, pgt_process_next)
+ pmu_group_csw_in_one(pgt->pgt_group, td, cpu, now);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+}
+
+void
+pmu_group_csw_in_complete(struct thread *td, int cpu)
+{
+ struct pmu_group_cpu_state *pgcs;
+ pmu_group_t *pg;
+
+ KASSERT(cpu >= 0 && cpu < MAXCPU,
+ ("[pmu] invalid csw-in completion CPU %d", cpu));
+ LIST_FOREACH(pgcs, &pmu_group_cpu_active[cpu], pgcs_next) {
+ if (pgcs->pgcs_td != td)
+ continue;
+ pg = pgcs->pgcs_group;
+ mtx_lock_spin(&pg->pg_snapshot_lock);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (pgcs->pgcs_counted && pgcs->pgcs_td == td &&
+ pgcs->pgcs_transitioning)
+ pgcs->pgcs_transitioning = false;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+ }
+}
+
+bool
+pmu_group_csw_can_start(struct pmc *pm, struct thread *td, int cpu)
+{
+ struct pmu_group_cpu_state *pgcs;
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ bool can_start;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || pe->pe_group == NULL)
+ return (true);
+ pg = pe->pe_group;
+ if (pg->pg_system || cpu < 0 || (u_int)cpu >= pg->pg_ncpu)
+ return (false);
+
+ pgcs = &pg->pg_cpu_state[cpu];
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ can_start = pg->pg_committed && pg->pg_running &&
+ !pg->pg_account_blocked && pg->pg_account_placement_admit &&
+ pgcs->pgcs_counted && pgcs->pgcs_td == td;
+ if (can_start && !pgcs->pgcs_placed) {
+ pmu_group_time_update_locked(pg, cpu_ticks());
+ pgcs->pgcs_placed = true;
+ pg->pg_running_threads++;
+ KASSERT(pg->pg_running_threads <= pg->pg_oncpu_threads,
+ ("[pmu] group %u running threads exceed on-CPU threads",
+ pg->pg_id));
+ }
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ return (can_start);
+}
+
+void
+pmu_group_csw_out(struct thread *td, int cpu)
+{
+ struct pmu_group_cpu_state *pgcs;
+ pmu_group_t *pg;
+
+ KASSERT(cpu >= 0 && cpu < MAXCPU,
+ ("[pmu] invalid csw-out CPU %d", cpu));
+ LIST_FOREACH(pgcs, &pmu_group_cpu_active[cpu], pgcs_next) {
+ if (pgcs->pgcs_td != td)
+ continue;
+ pg = pgcs->pgcs_group;
+ /* Do not wait for a snapshot during a context switch. */
+ mtx_lock_spin(&pg->pg_snapshot_lock);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (pgcs->pgcs_counted && pgcs->pgcs_td == td &&
+ !pgcs->pgcs_transitioning)
+ pgcs->pgcs_transitioning = true;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+ }
+}
+
+void
+pmu_group_csw_out_complete(struct thread *td, int cpu)
+{
+ struct pmu_group_cpu_state *pgcs, *next;
+ pmu_group_t *pg;
+ uint64_t now;
+
+ KASSERT(cpu >= 0 && cpu < MAXCPU,
+ ("[pmu] invalid csw-out completion CPU %d", cpu));
+ now = cpu_ticks();
+ LIST_FOREACH_SAFE(pgcs, &pmu_group_cpu_active[cpu], pgcs_next,
+ next) {
+ if (pgcs->pgcs_td != td)
+ continue;
+ pg = pgcs->pgcs_group;
+ mtx_lock_spin(&pg->pg_snapshot_lock);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (!pgcs->pgcs_counted || pgcs->pgcs_td != td) {
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+ continue;
+ }
+ pmu_group_time_update_locked(pg, now);
+ KASSERT(pg->pg_oncpu_threads > 0,
+ ("[pmu] group %u on-CPU thread underflow", pg->pg_id));
+ pg->pg_oncpu_threads--;
+ if (pgcs->pgcs_placed) {
+ KASSERT(pg->pg_running_threads > 0,
+ ("[pmu] group %u running thread underflow",
+ pg->pg_id));
+ pg->pg_running_threads--;
+ }
+ LIST_REMOVE(pgcs, pgcs_next);
+ pgcs->pgcs_td = NULL;
+ pgcs->pgcs_counted = false;
+ pgcs->pgcs_placed = false;
+ pgcs->pgcs_transitioning = false;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ mtx_unlock_spin(&pg->pg_snapshot_lock);
+ }
+}
+
+void
+pmu_group_on_release(struct pmc *pm)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL)
+ return;
+ pg = pe->pe_group;
+ if (pg != NULL) {
+ KASSERT(!pg->pg_committed,
+ ("[pmu] committed group %u released by member", pg->pg_id));
+ TAILQ_REMOVE(&pg->pg_events, pe, pe_sibling);
+ if (pg->pg_leader == pe)
+ pg->pg_leader = NULL;
+ KASSERT(pg->pg_nevents > 0,
+ ("[pmu] empty group %u member release", pg->pg_id));
+ pg->pg_nevents--;
+ pe->pe_group = NULL;
+ pe->pe_is_leader = false;
+ }
+ pm->pm_pmu = NULL;
+ pmu_event_destroy(pe);
+ if (pg != NULL && pg->pg_nevents == 0)
+ pmu_group_release(pg);
+}
+
+/*
+ * This function finds saved sampling progress.
+ * pp_tdslock protects both the thread list and the residual list.
+ */
+static struct pmu_thread_residual *
+pmu_thread_residual_find_locked(struct pmc_process *pp, pmu_event_t *pe,
+ lwpid_t tid)
+{
+ struct pmu_thread_residual *ptr;
+
+ mtx_assert(pp->pp_tdslock, MA_OWNED);
+ LIST_FOREACH(ptr, &pp->pp_pmu_residuals, ptr_next) {
+ if (ptr->ptr_event == pe && ptr->ptr_tid == tid)
+ return (ptr);
+ }
+ return (NULL);
+}
+
+static bool
+pmu_thread_exists_locked(struct pmc_process *pp, lwpid_t tid)
+{
+ struct pmc_thread *pt;
+
+ mtx_assert(pp->pp_tdslock, MA_OWNED);
+ LIST_FOREACH(pt, &pp->pp_tds, pt_next) {
+ if (pt->pt_td->td_tid == tid)
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * This function publishes or replaces one saved value.
+ * pp_tdslock is a spin lock. Allocate memory before you take this lock.
+ * At thread exit, remove the thread descriptor first. This stops a
+ * concurrent save from creating stale state again.
+ */
+static int
+pmu_event_set_thread_residual_impl(struct pmc *pm, struct pmc_process *pp,
+ lwpid_t tid, pmc_value_t value, uint8_t state)
+{
+ struct pmu_thread_residual *discard, *ptr;
+ pmu_event_t *pe;
+ bool keep;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || value > pm->pm_sc.pm_reloadcount)
+ return (EINVAL);
+
+ /*
+ * Keep an entry only for a partial count, or for a pending overflow.
+ * The UNINITIALIZED state removes any existing entry.
+ * Check the value and the state together. This stops a zero value
+ * from looking like an overflow, and stops the opposite case too.
+ */
+ switch (state) {
+ case PMU_RESIDUAL_UNINITIALIZED:
+ if (value != 0)
+ return (EINVAL);
+ keep = false;
+ break;
+ case PMU_RESIDUAL_COUNT_VALID:
+ if (value == 0)
+ return (EINVAL);
+ keep = true;
+ break;
+ case PMU_RESIDUAL_OVERFLOW_PENDING:
+ if (value != 0)
+ return (EINVAL);
+ keep = true;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ ptr = keep ? malloc(sizeof(*ptr), M_PMU, M_WAITOK | M_ZERO) : NULL;
+ discard = NULL;
+ mtx_lock_spin(pp->pp_tdslock);
+ if (!pmu_thread_exists_locked(pp, tid)) {
+ mtx_unlock_spin(pp->pp_tdslock);
+ free(ptr, M_PMU);
+ return (ESRCH);
+ }
+
+ discard = pmu_thread_residual_find_locked(pp, pe, tid);
+ if (!keep) {
+ if (discard != NULL) {
+ LIST_REMOVE(discard, ptr_next);
+ }
+ } else if (discard != NULL) {
+ discard->ptr_value = value;
+ discard->ptr_state = state;
+ discard = ptr;
+ ptr = NULL;
+ } else {
+ ptr->ptr_event = pe;
+ ptr->ptr_tid = tid;
+ ptr->ptr_value = value;
+ ptr->ptr_state = state;
+ LIST_INSERT_HEAD(&pp->pp_pmu_residuals, ptr, ptr_next);
+ ptr = NULL;
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+
+ if (discard != NULL)
+ free(discard, M_PMU);
+ return (0);
+}
+
+/*
+ * This function saves the progress of each target thread.
+ * First, take a snapshot of the row-indexed values under pp_tdslock.
+ * Then release the lock and allocate the stable entries.
+ */
+void
+pmu_event_save_residual(struct pmc *pm, struct pmc_process *pp, int ri)
+{
+ struct pmu_thread_residual_snapshot {
+ lwpid_t tid;
+ pmc_value_t value;
+ } *snapshot;
+ pmu_event_t *pe;
+ struct pmc_thread *pt;
+ u_int capacity, i, nthreads;
+ bool retry;
+ int error __diagused;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL)
+ return;
+
+ for (;;) {
+ capacity = 0;
+ mtx_lock_spin(pp->pp_tdslock);
+ LIST_FOREACH(pt, &pp->pp_tds, pt_next)
+ capacity++;
+ mtx_unlock_spin(pp->pp_tdslock);
+ if (capacity == 0)
+ return;
+
+ snapshot = mallocarray(capacity, sizeof(*snapshot), M_PMU,
+ M_WAITOK);
+ nthreads = 0;
+ retry = false;
+ mtx_lock_spin(pp->pp_tdslock);
+ LIST_FOREACH(pt, &pp->pp_tds, pt_next) {
+ if (nthreads == capacity) {
+ retry = true;
+ break;
+ }
+ snapshot[nthreads].tid = pt->pt_td->td_tid;
+ snapshot[nthreads].value = pt->pt_pmcs[ri].pt_pmcval;
+ nthreads++;
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+ if (!retry)
+ break;
+ free(snapshot, M_PMU);
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ if (snapshot[i].value == 0 ||
+ snapshot[i].value > pm->pm_sc.pm_reloadcount)
+ continue;
+ error = pmu_event_set_thread_residual_impl(pm, pp,
+ snapshot[i].tid, snapshot[i].value,
+ PMU_RESIDUAL_COUNT_VALID);
+ KASSERT(error == 0 || error == ESRCH,
+ ("[pmu] residual save failed for tid %d: %d",
+ snapshot[i].tid, error));
+ }
+ free(snapshot, M_PMU);
+}
+
+/*
+ * Save a system-mode sampling member's progress. Read it straight off
+ * the hardware at eviction. A value outside (0, reloadcount] means
+ * the counter overflowed, or was never seeded, so the next placement
+ * should start from a full period.
+ */
+void
+pmu_event_set_residual(struct pmc *pm, pmc_value_t residual)
+{
+ pmu_event_t *pe;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL)
+ return;
+ pe->pe_residual = (residual > 0 &&
+ residual <= pm->pm_sc.pm_reloadcount) ? residual : 0;
+}
+
+/*
+ * Seed the per-thread counts that a sampling member resumes from. A
+ * context switch reads pt_pmcval, and falls back to the per-process
+ * value only when the thread has no descriptor. Restoring only the
+ * per-process value would be ignored for every real thread.
+ */
+void
+pmu_event_restore_thread_residual(struct pmc *pm, struct pmc_process *pp,
+ int ri)
+{
+ struct pmu_thread_residual *ptr;
+ pmu_event_t *pe;
+ struct pmc_thread *pt;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL)
+ return;
+ mtx_lock_spin(pp->pp_tdslock);
+ LIST_FOREACH(pt, &pp->pp_tds, pt_next) {
+ ptr = pmu_thread_residual_find_locked(pp, pe,
+ pt->pt_td->td_tid);
+ /*
+ * A partial count resumes from the saved point.
+ * Both the overflow-pending state and the no-saved-progress
+ * state reload a full period.
+ */
+ pt->pt_pmcs[ri].pt_pmcval =
+ (ptr != NULL && ptr->ptr_state == PMU_RESIDUAL_COUNT_VALID) ?
+ ptr->ptr_value : pm->pm_sc.pm_reloadcount;
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+}
+
+/*
+ * The count a sampling member resumes from at placement: its saved
+ * progress, or a full period if it has none yet.
+ */
+pmc_value_t
+pmu_event_restore_residual(struct pmc *pm)
+{
+ pmu_event_t *pe;
+
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || pe->pe_residual == 0 ||
+ pe->pe_residual > pm->pm_sc.pm_reloadcount)
+ return (pm->pm_sc.pm_reloadcount);
+ return (pe->pe_residual);
+}
+
+#ifdef HWPMC_DEBUG
+int
+pmu_event_get_thread_residual(struct pmc *pm, struct pmc_process *pp,
+ lwpid_t tid, pmc_value_t *value, uint8_t *state)
+{
+ struct pmu_thread_residual *ptr;
+ pmu_event_t *pe;
+ int error;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pe = pmu_event_from_pmc(pm);
+ if (pe == NULL || value == NULL || state == NULL)
+ return (EINVAL);
+
+ error = 0;
+ mtx_lock_spin(pp->pp_tdslock);
+ if (!pmu_thread_exists_locked(pp, tid))
+ error = ESRCH;
+ else {
+ ptr = pmu_thread_residual_find_locked(pp, pe, tid);
+ if (ptr != NULL) {
+ *value = ptr->ptr_value;
+ *state = ptr->ptr_state;
+ } else {
+ *value = 0;
+ *state = PMU_RESIDUAL_UNINITIALIZED;
+ }
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+ return (error);
+}
+
+int
+pmu_event_set_thread_residual(struct pmc *pm, struct pmc_process *pp,
+ lwpid_t tid, pmc_value_t value, uint8_t state)
+{
+
+ return (pmu_event_set_thread_residual_impl(pm, pp, tid, value, state));
+}
+#endif
+
+static void
+pmu_thread_residual_free_task_fn(void *arg __unused, int pending __unused)
+{
+ struct pmu_thread_residual *ptr;
+ LIST_HEAD(, pmu_thread_residual) tmplist;
+
+ LIST_INIT(&tmplist);
+ mtx_lock_spin(&pmu_residual_freelist_mtx);
+ while ((ptr = LIST_FIRST(&pmu_residual_freelist)) != NULL) {
+ LIST_REMOVE(ptr, ptr_next);
+ LIST_INSERT_HEAD(&tmplist, ptr, ptr_next);
+ }
+ mtx_unlock_spin(&pmu_residual_freelist_mtx);
+
+ while ((ptr = LIST_FIRST(&tmplist)) != NULL) {
+ LIST_REMOVE(ptr, ptr_next);
+ free(ptr, M_PMU);
+ }
+}
+
+static void
+pmu_thread_residual_defer_free(struct pmu_thread_residual *ptr)
+{
+
+ mtx_lock_spin(&pmu_residual_freelist_mtx);
+ LIST_INSERT_HEAD(&pmu_residual_freelist, ptr, ptr_next);
+ taskqueue_enqueue(taskqueue_fast, &pmu_residual_free_task);
+ mtx_unlock_spin(&pmu_residual_freelist_mtx);
+}
+
+void
+pmu_thread_residual_remove(struct pmc_process *pp, lwpid_t tid)
+{
+ struct pmu_thread_residual *ptr;
+
+ for (;;) {
+ mtx_lock_spin(pp->pp_tdslock);
+ LIST_FOREACH(ptr, &pp->pp_pmu_residuals, ptr_next) {
+ if (ptr->ptr_tid == tid)
+ break;
+ }
+ if (ptr != NULL) {
+ LIST_REMOVE(ptr, ptr_next);
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+ if (ptr == NULL)
+ break;
+ pmu_thread_residual_defer_free(ptr);
+ }
+}
+
+static void
+pmu_event_remove_target_residuals(pmu_event_t *pe, struct pmc_process *pp)
+{
+ struct pmu_thread_residual *ptr;
+
+ for (;;) {
+ mtx_lock_spin(pp->pp_tdslock);
+ LIST_FOREACH(ptr, &pp->pp_pmu_residuals, ptr_next) {
+ if (ptr->ptr_event == pe)
+ break;
+ }
+ if (ptr != NULL) {
+ LIST_REMOVE(ptr, ptr_next);
+ }
+ mtx_unlock_spin(pp->pp_tdslock);
+ if (ptr == NULL)
+ break;
+ free(ptr, M_PMU);
+ }
+}
+
+/*
+ * Attach all sibling events of a group to the process descriptor.
+ */
+static void
+pmu_group_attach_siblings(pmu_group_t *pg, struct pmc_process *anchor_pp)
+{
+ pmu_event_t *pe;
+ struct pmc *pm;
+ struct pmc_process *pp;
+ struct pmu_group_target *pgt;
+
+ KASSERT(anchor_pp == pg->pg_pp,
+ ("[pmu] group %u anchor mismatch", pg->pg_id));
+ LIST_FOREACH(pgt, &pg->pg_targets, pgt_group_next) {
+ pp = pgt->pgt_pp;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pm = pe->pe_pmc;
+ KASSERT(!PMC_ROW_IS_UNASSIGNED(pm),
+ ("[pmu] attach_siblings: gid=%u pm_id=0x%jx "
+ "unassigned", pg->pg_id, (uintmax_t)pm->pm_id));
+ if (pp->pp_pmcs[PMC_TO_ROWINDEX(pm)].pp_pmc == pm)
+ continue; /* Already attached at this placement. */
+ KASSERT(pp->pp_pmcs[PMC_TO_ROWINDEX(pm)].pp_pmc == NULL,
+ ("[pmu] group %u target pp=%p row=%d busy",
+ pg->pg_id, pp, PMC_TO_ROWINDEX(pm)));
+ pmc_rotation_attach(pm, pp);
+ }
+ }
+}
+
+/*
+ * Schedule a group in by assigning hardware rows for all siblings.
+ */
+static int
+pmu_pp_schedule_in(struct pmc_process *pp, pmu_group_t *pg)
+{
+ pmu_event_t *pe;
+ struct proc *p;
+ int error;
+
+ if (pg == NULL || pg->pg_assigned)
+ return (0);
+ /* Use the target process only. */
+ p = pmu_group_target_proc(pg);
+ if (p == NULL)
+ return (EINVAL);
+ KASSERT(pp != NULL && pp->pp_proc == p,
+ ("[pmu] schedule_in: pp/p mismatch pp=%p pp->proc=%p p=%p",
+ pp, pp != NULL ? pp->pp_proc : NULL, p));
+
+ error = pmu_group_can_place(pg, p, PMC_CPU_ANY);
+ if (error != 0)
+ return (error);
+
+ error = pmu_assign_group(pg, p, PMC_CPU_ANY);
+ if (error == EBUSY)
+ error = ENOSPC;
+ if (error != 0) {
+ PMCDBG2(PMC, OPS, 1,
+ "pp_schedule_in: assign gid=%u err=%d",
+ pg->pg_id, error);
+ return (error);
+ }
+
+ /* Set the running state before you publish the rows. */
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_RUNNING;
+
+ pmu_group_attach_siblings(pg, pp);
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ pg->pg_account_placement_admit = true;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ pmu_group_kick_placement(pg);
+ return (0);
+}
+
+/*
+ * Schedule a group out and release assigned hardware rows.
+ */
+static void
+pmu_pp_schedule_out(struct pmc_process *pp, pmu_group_t *pg,
+ bool drain_samples)
+{
+ pmu_event_t *pe;
+ struct pmc_process *target_pp;
+ struct pmu_group_target *pgt;
+
+ if (pg == NULL || !pg->pg_assigned)
+ return;
+
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ pmu_group_time_update_locked(pg, cpu_ticks());
+ pg->pg_account_placement_admit = false;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+
+ /* Phase 1: stop the sibling events. */
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ if (PMC_IS_SAMPLING_MODE(PMC_TO_MODE(pe->pe_pmc))) {
+ pmc_rotation_drain_set(pe->pe_pmc, drain_samples);
+ wmb();
+ }
+ pe->pe_pmc->pm_state = PMC_STATE_STOPPED;
+ }
+
+ /* Phase 2: flush the log and drain the context switches. */
+ if (pg->pg_owner != NULL &&
+ (pg->pg_owner->po_flags & PMC_PO_OWNS_LOGFILE) != 0)
+ (void)pmclog_flush(pg->pg_owner, 1);
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pmc_rotation_drain(pe->pe_pmc);
+
+ /* Phase 3: remove every transient row link from every stable target. */
+ KASSERT(pp == pg->pg_pp,
+ ("[pmu] group %u schedule-out anchor mismatch", pg->pg_id));
+ LIST_FOREACH(pgt, &pg->pg_targets, pgt_group_next) {
+ target_pp = pgt->pgt_pp;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ struct pmc *pm = pe->pe_pmc;
+
+ KASSERT(!PMC_ROW_IS_UNASSIGNED(pm),
+ ("[pmu] schedule_out: gid=%u pm_id=0x%jx "
+ "unassigned", pg->pg_id, (uintmax_t)pm->pm_id));
+ if (target_pp->pp_pmcs[PMC_TO_ROWINDEX(pm)].pp_pmc == pm)
+ pmc_rotation_detach(pm, target_pp);
+ }
+ }
+
+ /* Phase 4: release the hardware rows. */
+ pmu_unassign_group(pg, 0);
+}
+
+/*
+ * Remove all groups from the process and stop the rotation thread.
+ */
+static void
+pmu_pp_release_all(struct pmc_process *pp)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ struct pmc_process *replacement_pp;
+ struct pmu_group_target *departing, *replacement;
+ bool was_running;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+
+ if (pp == NULL)
+ return;
+
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ LIST_FOREACH(pg, &pp->pp_pmu_groups, pg_proc_next)
+ pmu_group_accounting_block(pg);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+
+ /* Stop the rotation thread before freeing the process descriptor. */
+ pmu_pp_stop_rotate(pp, "muxpurge");
+ while (pp->pp_pmu_refs != 0)
+ (void)sx_sleep(&pp->pp_pmu_refs, &pmc_sx, 0, "muxrefs", 1);
+
+ /* Schedule out assigned groups and unhook every group. */
+ for (;;) {
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ pg = LIST_FIRST(&pp->pp_pmu_groups);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ if (pg == NULL)
+ break;
+
+ departing = pmu_group_find_target(pg, pp);
+ KASSERT(departing != NULL,
+ ("[pmu] group %u anchor has no target edge", pg->pg_id));
+ replacement = pmu_group_replacement_target(pg, pp);
+ replacement_pp = replacement != NULL ? replacement->pgt_pp : NULL;
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ was_running = pg->pg_running;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ pmu_group_accounting_drain(pg, true);
+ if (pg->pg_assigned)
+ pmu_pp_schedule_out(pp, pg, false);
+ pmu_pp_unlink_group(pg, pp);
+ pmu_group_target_remove(departing);
+ if (replacement_pp == NULL)
+ continue;
+
+ /*
+ * pmc_sx serializes target-list changes and process teardown.
+ * Change the old and new pp_pmu_groups lists one at a time, so
+ * their spin locks are never held together.
+ */
+ pmu_pp_link_group(pg, replacement_pp);
+ if (was_running) {
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_RUNNING;
+ }
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (was_running)
+ pmu_group_running_start_locked(pg, cpu_ticks());
+ pg->pg_account_placement_admit = pg->pg_assigned;
+ pg->pg_account_blocked = false;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ pmu_pp_backfill(replacement_pp);
+ }
+}
+
+/* Initialize the PMU fields in the process descriptor. */
+void
+pmu_pp_init(struct pmc_process *pp)
+{
+
+ mtx_init(&pp->pp_pmu_lock, "pmc-pmu-groups", "pmc-pmu", MTX_SPIN);
+ LIST_INIT(&pp->pp_pmu_groups);
+ LIST_INIT(&pp->pp_pmu_targets);
+ LIST_INIT(&pp->pp_pmu_residuals);
+}
+
+/* Destroy the PMU state in the process descriptor. */
+void
+pmu_pp_destroy(struct pmc_process *pp)
+{
+
+ pmu_pp_release_all(pp);
+ /* Remove every authoritative edge after the anchored groups stop. */
+ pmu_group_disinherit(pp);
+ KASSERT(LIST_EMPTY(&pp->pp_pmu_residuals),
+ ("[pmu] pp %p destroyed with saved residuals", pp));
+ KASSERT(pp->pp_pmu_refs == 0 && pp->pp_pmu_rot_quiesce == 0 &&
+ pp->pp_pmu_rot_td == NULL,
+ ("[pmu] pp %p destroyed while referenced", pp));
+ mtx_destroy(&pp->pp_pmu_lock);
+}
+
+void
+pmu_group_detach_target(pmu_group_t *pg, struct pmc_process *pp)
+{
+ pmu_event_t *pe;
+ struct pmu_group_target *pgt;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pg != NULL && pp != NULL && pg->pg_pp == pp,
+ ("[pmu] invalid group target detach"));
+ KASSERT(!pg->pg_releasing,
+ ("[pmu] group %u detach already active", pg->pg_id));
+ pgt = pmu_group_find_target(pg, pp);
+ KASSERT(pgt != NULL && pgt->pgt_explicit,
+ ("[pmu] group %u explicit target edge missing", pg->pg_id));
+
+ pg->pg_releasing = true;
+ mtx_lock_spin(&pp->pp_pmu_lock);
+ pmu_group_accounting_block(pg);
+ mtx_unlock_spin(&pp->pp_pmu_lock);
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_STOPPED;
+ pmu_group_accounting_drain(pg, false);
+ pmu_pp_stop_rotate(pp, "muxdetach");
+ if (pg->pg_assigned)
+ pmu_pp_schedule_out(pp, pg, false);
+
+ pmu_group_target_remove(pgt);
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pe->pe_pmc->pm_flags &= ~(PMC_F_ATTACH_DONE |
+ PMC_F_ATTACHED_TO_OWNER | PMC_F_NEEDS_LOGFILE);
+ }
+ pg->pg_releasing = false;
+ wakeup(&pg->pg_releasing);
+
+ pmu_pp_backfill(pp);
+}
+
+/*
+ * This function detaches one inherited target from a group. This target
+ * is not the anchor. The group stays alive for its other targets.
+ * For example, use this function when a credential-changing exec revokes
+ * one descendant's authorization. Only this process's edge and row links
+ * are removed.
+ *
+ * The function briefly schedules the group out at its real anchor. This
+ * drops every target's row links. The function then removes this edge.
+ * Then the function puts the group back at the surviving anchor.
+ */
+void
+pmu_group_detach_inherited_target(pmu_group_t *pg, struct pmc_process *pp)
+{
+ pmu_event_t *pe;
+ struct pmc_process *anchor_pp;
+ struct pmu_group_target *pgt;
+ bool was_running, was_assigned;
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ KASSERT(pg != NULL && pp != NULL && pg->pg_pp != pp,
+ ("[pmu] inherited detach on anchor pp=%p", pp));
+ KASSERT(!pg->pg_releasing,
+ ("[pmu] group %u detach already active", pg->pg_id));
+ pgt = pmu_group_find_target(pg, pp);
+ KASSERT(pgt != NULL && !pgt->pgt_explicit,
+ ("[pmu] group %u inherited target edge missing", pg->pg_id));
+
+ anchor_pp = pg->pg_pp;
+ pg->pg_releasing = true;
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ was_running = pg->pg_running;
+ was_assigned = pg->pg_assigned;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+
+ /* First, quiesce the group at the anchor. Then drop every target's row links. */
+ if (anchor_pp != NULL) {
+ pmu_group_accounting_block(pg);
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_STOPPED;
+ pmu_group_accounting_drain(pg, false);
+ pmu_pp_stop_rotate(anchor_pp, "muxreauth");
+ if (was_assigned)
+ pmu_pp_schedule_out(anchor_pp, pg, false);
+ }
+
+ pmu_group_target_remove(pgt);
+
+ /*
+ * Restore the group for the remaining targets.
+ * This edge was never the anchor, so pg_pp does not change.
+ */
+ if (was_running) {
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling)
+ pe->pe_pmc->pm_state = PMC_STATE_RUNNING;
+ }
+ mtx_pool_lock_spin(pmc_mtxpool, pg);
+ if (was_running)
+ pmu_group_running_start_locked(pg, cpu_ticks());
+ pg->pg_account_placement_admit = pg->pg_assigned;
+ pg->pg_account_blocked = false;
+ mtx_pool_unlock_spin(pmc_mtxpool, pg);
+ pg->pg_releasing = false;
+ wakeup(&pg->pg_releasing);
+ if (anchor_pp != NULL)
+ pmu_pp_backfill(anchor_pp);
+}
+
+/* Return true if a running MUX group is unassigned. */
+static bool
+pmu_list_has_deferred(struct pmu_group_list *gl)
+{
+ pmu_group_t *pg;
+
+ LIST_FOREACH(pg, gl, pg_proc_next) {
+ if (pg->pg_running && !pg->pg_assigned && pg->pg_defer_ok)
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Start or wake the process rotation thread.
+ */
+static void
+pmu_pp_kick_rotate(struct pmc_process *pp)
+{
+ int error;
+
+ if (pp == NULL || pp->pp_pmu_rot_quiesce != 0 || pp->pp_pmu_unhashed)
+ return;
+ if (!pmu_list_has_deferred(&pp->pp_pmu_groups))
+ return;
+
+ pp->pp_pmu_rot_needed = true;
+ if (pp->pp_pmu_rot_running || pp->pp_pmu_rot_td != NULL) {
+ wakeup(&pp->pp_pmu_rot_needed);
+ return;
+ }
+
+ pp->pp_pmu_rot_running = true;
+ error = kthread_add(pmu_pp_rotate_thread, pp, NULL,
+ &pp->pp_pmu_rot_td, 0, 0, "pmu_rot_pp_%d",
+ pp->pp_proc != NULL ? pp->pp_proc->p_pid : -1);
+ if (error != 0) {
+ pp->pp_pmu_rot_running = false;
+ pp->pp_pmu_rot_td = NULL;
+ PMCDBG1(PMC, OPS, 1,
+ "pp_kick_rotate: kthread_add err=%d", error);
+ }
+}
+
+void
+pmu_pp_kick_after_exec(struct pmc_process *pp)
+{
+
+ sx_assert(&pmc_sx, SX_XLOCKED);
+ pmu_pp_kick_rotate(pp);
+}
+
+/*
+ * Return the bitmask of rows held by assigned MUX groups.
+ */
+static uint64_t
+pmu_list_evictable_rows(struct pmu_group_list *gl)
+{
+ pmu_event_t *pe;
+ pmu_group_t *pg;
+ uint64_t evictable;
+ u_int n;
+
+ evictable = 0;
+ LIST_FOREACH(pg, gl, pg_proc_next) {
+ if (!pg->pg_assigned || !pg->pg_defer_ok)
+ continue;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ n = PMC_TO_ROWINDEX(pe->pe_pmc);
+ if (n < 64)
+ evictable |= 1ULL << n;
+ }
+ }
+ return (evictable);
+}
+
+/*
+ * Find the next assigned MUX group to evict.
+ */
+static pmu_group_t *
+pmu_list_next_victim(struct pmu_group_list *gl, pmu_group_t **vpg,
+ u_int *vseen, u_int ngroups)
+{
+ pmu_group_t *pg;
+
+ while (*vseen < ngroups) {
+ pg = *vpg;
+ if (pg == NULL)
+ pg = LIST_FIRST(gl);
+ (*vseen)++;
+ *vpg = LIST_NEXT(pg, pg_proc_next);
+ if (pg->pg_assigned && pg->pg_defer_ok)
+ return (pg);
+ }
+ return (NULL);
+}
+
+/*
+ * Run one multiplex rotation tick for a process.
+ * Evicts assigned MUX groups and assigns deferred MUX groups.
+ */
+static void
+pmu_pp_rotate_one(struct pmc_process *pp)
+{
+ pmu_group_t *cursor, *pg, *victim, *vpg;
+ struct proc *p;
+ uint64_t evictable;
+ u_int ngroups, seen, vseen;
+ int sin_err;
+ bool found_cursor, placed_any, satisfiable;
+
+ if (pp == NULL)
+ return;
+ if (LIST_EMPTY(&pp->pp_pmu_groups)) {
+ pp->pp_pmu_rot_needed = false;
+ return;
+ }
+
+ /* Reset the cursor to the head if it is invalid. */
+ ngroups = 0;
+ cursor = pp->pp_pmu_rot_cursor;
+ found_cursor = false;
+ LIST_FOREACH(pg, &pp->pp_pmu_groups, pg_proc_next) {
+ if (pg == cursor)
+ found_cursor = true;
+ ngroups++;
+ }
+ if (!found_cursor)
+ cursor = LIST_FIRST(&pp->pp_pmu_groups);
+
+ /* Do not rotate if no deferred group can run. */
+ evictable = pmu_list_evictable_rows(&pp->pp_pmu_groups);
+ satisfiable = false;
+ LIST_FOREACH(pg, &pp->pp_pmu_groups, pg_proc_next) {
+ if (!pg->pg_running || pg->pg_assigned || !pg->pg_defer_ok)
+ continue;
+ p = pmu_group_target_proc(pg);
+ if (p != NULL && pmu_group_satisfiable(pg, p, PMC_CPU_ANY,
+ evictable) == 0) {
+ satisfiable = true;
+ break;
+ }
+ }
+ if (!satisfiable)
+ goto out;
+
+ /* Evict group at cursor and advance cursor. */
+ vpg = cursor;
+ vseen = 0;
+ victim = pmu_list_next_victim(&pp->pp_pmu_groups, &vpg, &vseen,
+ ngroups);
+ if (victim != NULL) {
+ PMCDBG3(PMC, OPS, 4, "rotate: pp=%p evict gid=%u nevents=%u",
+ pp, victim->pg_id, victim->pg_nevents);
+ pmu_pp_schedule_out(pp, victim, true);
+ cursor = LIST_NEXT(victim, pg_proc_next);
+ if (cursor == NULL)
+ cursor = LIST_FIRST(&pp->pp_pmu_groups);
+ }
+
+ /* Assign deferred MUX groups in order. */
+ placed_any = false;
+ pg = cursor;
+ seen = 0;
+ while (seen < ngroups) {
+ if (pg == NULL)
+ pg = LIST_FIRST(&pp->pp_pmu_groups);
+ seen++;
+ if (!pg->pg_running || pg->pg_assigned || !pg->pg_defer_ok)
+ goto next;
+ p = pmu_group_target_proc(pg);
+ if (p == NULL || pmu_group_satisfiable(pg, p, PMC_CPU_ANY,
+ evictable) != 0)
+ goto next;
+ sin_err = pmu_pp_schedule_in(pp, pg);
+ while (sin_err == ENOSPC && !placed_any) {
+ victim = pmu_list_next_victim(&pp->pp_pmu_groups,
+ &vpg, &vseen, ngroups);
+ if (victim == NULL)
+ break;
+ PMCDBG2(PMC, OPS, 4,
+ "rotate: pp=%p escalate evict gid=%u",
+ pp, victim->pg_id);
+ pmu_pp_schedule_out(pp, victim, true);
+ sin_err = pmu_pp_schedule_in(pp, pg);
+ }
+ PMCDBG4(PMC, OPS, 4,
+ "rotate: pp=%p schedule_in gid=%u nevents=%u -> %d",
+ pp, pg->pg_id, pg->pg_nevents, sin_err);
+ if (sin_err == 0) {
+ placed_any = true;
+ } else if (sin_err == ENOSPC) {
+ pp->pp_pmu_rot_cursor = pg;
+ goto out;
+ }
+ /* Skip group on hard error. */
+next:
+ pg = LIST_NEXT(pg, pg_proc_next);
+ }
+ pp->pp_pmu_rot_cursor = cursor;
+out:
+ /* Stop thread if all groups are assigned. */
+ pp->pp_pmu_rot_needed = pmu_list_has_deferred(&pp->pp_pmu_groups);
+}
+
+/*
+ * Rotation thread for process-mode groups.
+ */
+static void
+pmu_pp_rotate_thread(void *arg)
+{
+ struct pmc_process *pp = arg;
+
+ sx_xlock(&pmc_sx);
+ while (pp->pp_pmu_rot_running) {
+ (void)sx_sleep(&pp->pp_pmu_rot_needed, &pmc_sx, 0, "muxrot",
+ pmu_rot_period_ticks());
+ if (!pp->pp_pmu_rot_running)
+ break;
+ pmu_pp_rotate_one(pp);
+ if (!pp->pp_pmu_rot_needed)
+ break;
+ }
+ pp->pp_pmu_rot_running = false;
+ pp->pp_pmu_rot_td = NULL;
+ wakeup(&pp->pp_pmu_rot_td);
+ sx_xunlock(&pmc_sx);
+ kthread_exit();
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Oct 20, 6:33 AM (10 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38586955
Default Alt Text
D57631.id185746.diff (73 KB)
Attached To
Mode
D57631: hwpmc: add PMU event/group structures and rotation runtime
Attached
Detach File
Event Timeline
Log In to Comment