Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176340908
D57632.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
D57632.diff
View Options
diff --git a/sys/dev/hwpmc/hwpmc_amd.c b/sys/dev/hwpmc/hwpmc_amd.c
--- a/sys/dev/hwpmc/hwpmc_amd.c
+++ b/sys/dev/hwpmc/hwpmc_amd.c
@@ -52,6 +52,8 @@
#include <machine/md_var.h>
#include <machine/specialreg.h>
+#include "hwpmc_pmu.h"
+
#define OVERFLOW_WAIT_COUNT 50
DPCPU_DEFINE_STATIC(uint32_t, nmi_counter);
@@ -609,6 +611,49 @@
return (0);
}
+/*
+ * Return scheduling constraints for AMD PMCs.
+ */
+static int
+amd_get_sched_constraint(struct pmc *pm __unused,
+ const struct pmc_op_pmcallocate *a, pmc_sched_constraint_t *cons)
+{
+ uint32_t mask;
+ u_int first, count, i;
+
+ if (cons == NULL || a == NULL)
+ return (EINVAL);
+ bzero(cons, sizeof(*cons));
+
+ switch (a->pm_md.pm_amd.pm_amd_sub_class) {
+ case PMC_AMD_SUB_CLASS_CORE:
+ first = 0;
+ count = amd_core_npmcs;
+ break;
+ case PMC_AMD_SUB_CLASS_L3_CACHE:
+ first = amd_core_npmcs;
+ count = amd_l3_npmcs;
+ break;
+ case PMC_AMD_SUB_CLASS_DATA_FABRIC:
+ first = amd_core_npmcs + amd_l3_npmcs;
+ count = amd_df_npmcs;
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ if (count == 0 || first + count > 32)
+ return (EOPNOTSUPP);
+
+ mask = 0;
+ for (i = 0; i < count; i++)
+ mask |= 1u << (first + i);
+ cons->pc_allowed_rows = mask;
+ cons->pc_weight = (uint8_t)count;
+ cons->pc_flags = PMC_SC_F_SHARED;
+ cons->pc_fixed_row = (uint8_t)first;
+ return (0);
+}
+
/*
* Check if a given PMC allocation is feasible.
*/
@@ -1680,6 +1725,10 @@
pcd->pcd_write_pmc = amd_write_pmc;
pcd->pcd_get_caps = amd_get_caps;
+ /* These are the grouping and multiplex constraint functions. */
+ pcd->pcd_can_assign_pmc = amd_can_assign_pmc;
+ pcd->pcd_get_sched_constraint = amd_get_sched_constraint;
+
pmc_mdep->pmd_cputype = cputype;
pmc_mdep->pmd_intr = amd_intr;
pmc_mdep->pmd_switch_in = amd_switch_in;
diff --git a/sys/dev/hwpmc/hwpmc_assign.c b/sys/dev/hwpmc/hwpmc_assign.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/hwpmc/hwpmc_assign.c
@@ -0,0 +1,450 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * This is the greedy row assigner for PMU event groups.
+ *
+ * Algorithm:
+ * 1. Read the event constraints from the class backend.
+ * 2. Sort the events by weight, lowest first.
+ * 3. Assign each event to the first free allowed row.
+ * 4. On assignment failure, release all assigned rows.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/pmc.h>
+#include <sys/proc.h>
+
+#include "hwpmc_pmu.h"
+
+/*
+ * Find the class descriptor for a PMC class.
+ */
+static struct pmc_classdep *
+pmu_class_to_classdep(enum pmc_class class)
+{
+ struct pmc_mdep *mdep;
+ struct pmc_classdep *pcd;
+ u_int i;
+
+ mdep = hwpmc_get_mdep();
+ if (mdep == NULL)
+ return (NULL);
+ for (i = 0; i < mdep->pmd_nclass; i++) {
+ pcd = &mdep->pmd_classdep[i];
+ if (pcd->pcd_class == class)
+ return (pcd);
+ }
+ return (NULL);
+}
+
+/*
+ * Ask the class backend if the row assignment is possible.
+ */
+static int
+pmu_can_assign_pmc(struct pmc_classdep *pcd, int adjri, struct pmc *pm,
+ const struct pmc_op_pmcallocate *a)
+{
+ if (pcd == NULL || pcd->pcd_can_assign_pmc == NULL)
+ return (0);
+ return (pcd->pcd_can_assign_pmc(adjri, pm, a));
+}
+
+/*
+ * Get the scheduling constraints for an event from the class backend.
+ */
+int
+pmu_event_get_constraint(pmu_event_t *pe,
+ pmc_sched_constraint_t *cons)
+{
+ const struct pmc_op_pmcallocate *a;
+ struct pmc_classdep *pcd;
+
+ if (pe == NULL || cons == NULL)
+ return (EINVAL);
+ a = &pe->pe_alloc;
+ pcd = pmu_class_to_classdep(a->pm_class);
+ if (pcd == NULL || pcd->pcd_get_sched_constraint == NULL)
+ return (EOPNOTSUPP);
+ return (pcd->pcd_get_sched_constraint(pe->pe_pmc, a, cons));
+}
+
+/*
+ * Return true if the PMC class supports event grouping.
+ */
+bool
+pmu_class_supports_grouping(enum pmc_class class)
+{
+ struct pmc_classdep *pcd;
+
+ pcd = pmu_class_to_classdep(class);
+ return (pcd != NULL && pcd->pcd_get_sched_constraint != NULL);
+}
+
+/*
+ * Assign one event to a hardware row.
+ */
+static int
+pmu_assign_one(pmu_group_t *pg, pmu_event_t *pe, struct proc *p, int cpu,
+ uint32_t *used_mask, bool dry_run, uint64_t evictable_rows)
+{
+ struct pmu_group_target *pgt;
+ struct pmc *pm;
+ struct pmc_classdep *pcd;
+ enum pmc_mode mode;
+ uint32_t allowed, free_mask;
+ int adjri, idcpu, n;
+ bool sys;
+
+ /*
+ * Constraints use the per-class row index (adjri).
+ * The global row index is pcd->pcd_ri + adjri.
+ */
+ pcd = pmu_class_to_classdep(pe->pe_alloc.pm_class);
+ if (pcd == NULL)
+ return (EBUSY);
+ pm = pe->pe_pmc;
+ mode = pe->pe_alloc.pm_mode;
+ allowed = pe->pe_cons.pc_allowed_rows;
+ /* System mode binds to one CPU. Virtual mode can use any CPU. */
+ sys = PMC_IS_SYSTEM_MODE(mode);
+ idcpu = sys ? cpu : PMC_CPU_ANY;
+
+ if ((pe->pe_cons.pc_flags & PMC_SC_F_FIXED) != 0) {
+ adjri = pe->pe_cons.pc_fixed_row;
+ if (adjri < 0 || adjri >= 32)
+ return (EBUSY);
+ if (((1u << adjri) & allowed) == 0 ||
+ (*used_mask & (1u << adjri)) != 0)
+ return (EBUSY);
+ } else {
+ adjri = -1;
+ }
+
+ for (;;) {
+ if (adjri < 0) {
+ free_mask = allowed & ~*used_mask;
+ if (free_mask == 0)
+ return (EBUSY);
+ adjri = ffs(free_mask) - 1;
+ }
+ if (adjri >= pcd->pcd_num)
+ goto skip;
+ n = pcd->pcd_ri + adjri;
+ if (p != NULL) {
+ if (!hwpmc_can_allocate_row(n, mode) &&
+ (n >= 64 ||
+ (evictable_rows & (1ULL << n)) == 0))
+ goto skip;
+ if (LIST_EMPTY(&pg->pg_targets)) {
+ if (!hwpmc_can_allocate_rowindex(p, n, idcpu) &&
+ (n >= 64 ||
+ (evictable_rows & (1ULL << n)) == 0))
+ goto skip;
+ } else {
+ LIST_FOREACH(pgt, &pg->pg_targets,
+ pgt_group_next) {
+ if (hwpmc_can_allocate_rowindex(
+ pgt->pgt_pp->pp_proc, n, idcpu))
+ continue;
+ if (n < 64 &&
+ (evictable_rows & (1ULL << n)) != 0)
+ continue;
+ goto skip;
+ }
+ }
+ }
+ /* Check the system row occupancy. */
+ if (sys && p != NULL && !hwpmc_row_is_unallocated(cpu, n) &&
+ (n >= 64 || (evictable_rows & (1ULL << n)) == 0))
+ goto skip;
+ if (pmu_can_assign_pmc(pcd, adjri, pm, &pe->pe_alloc) != 0)
+ goto skip;
+ if (!dry_run &&
+ pcd->pcd_allocate_pmc(cpu, adjri, pm, &pe->pe_alloc) != 0)
+ goto skip;
+
+ *used_mask |= 1u << adjri;
+ if (dry_run)
+ return (0);
+ pm->pm_id = PMC_ID_MAKE_ID(idcpu, mode,
+ pe->pe_alloc.pm_class, n);
+ /* Mark the row STANDALONE for system mode, or THREAD for virtual mode. */
+ if (sys)
+ hwpmc_mark_row_standalone(n);
+ else
+ hwpmc_mark_row_thread(n);
+ return (0);
+
+skip:
+ if ((pe->pe_cons.pc_flags & PMC_SC_F_FIXED) != 0)
+ return (EBUSY);
+ allowed &= ~(1u << adjri);
+ adjri = -1;
+ }
+}
+
+/*
+ * Release all hardware rows assigned to a group.
+ */
+void
+pmu_unassign_group(pmu_group_t *pg, int cpu)
+{
+ pmu_event_t *pe;
+ struct pmc *pm;
+ struct pmc_classdep *pcd;
+ enum pmc_mode mode;
+ int adjri, n;
+ bool sys;
+
+ sys = (pg != NULL && pg->pg_system);
+
+ /* Set the CPU index to 0 for virtual mode. */
+ if (cpu < 0)
+ cpu = 0;
+
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ pm = pe->pe_pmc;
+ if (PMC_ROW_IS_UNASSIGNED(pm))
+ continue;
+ n = PMC_TO_ROWINDEX(pm);
+ mode = PMC_TO_MODE(pm);
+ pcd = hwpmc_ri_to_classdep(n, &adjri);
+ if (pcd != NULL) {
+ /*
+ * Clear the configuration before you release the row.
+ * For virtual mode, unconfigure all CPUs.
+ */
+ if (sys)
+ (void)pcd->pcd_config_pmc(cpu, adjri, NULL);
+ else
+ hwpmc_unconfigure_row_all_cpus(pm, n);
+ (void)pcd->pcd_release_pmc(cpu, adjri, pm);
+ }
+ pm->pm_id = PMC_ID_MAKE_ID(PMC_CPU_ANY, mode,
+ pe->pe_alloc.pm_class, PMC_ROW_UNASSIGNED);
+ /* Clear the row disposition. */
+ if (sys)
+ hwpmc_unmark_row_standalone(n);
+ else
+ hwpmc_unmark_row_thread(n);
+ }
+ pg->pg_assigned = false;
+}
+
+/*
+ * Sort events by constraint weight, lowest first.
+ */
+static void
+pmu_sort_by_weight(pmu_group_t *pg, pmu_event_t **order, u_int n)
+{
+ pmu_event_t *pe, *cur;
+ u_int i, j;
+
+ i = 0;
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ if (i >= n)
+ break;
+ order[i++] = pe;
+ }
+ for (i = 1; i < n; i++) {
+ cur = order[i];
+ j = i;
+ while (j > 0 &&
+ order[j - 1]->pe_cons.pc_weight > cur->pe_cons.pc_weight) {
+ order[j] = order[j - 1];
+ j--;
+ }
+ order[j] = cur;
+ }
+}
+
+static int
+pmu_group_probe(pmu_group_t *pg, struct proc *p, int cpu,
+ uint64_t evictable_rows)
+{
+ pmu_event_t *order[PMC_GROUP_MAX_MEMBERS];
+ uint32_t used_mask;
+ u_int i;
+ int error;
+
+ if (pg == NULL || pg->pg_nevents == 0)
+ return (EINVAL);
+ KASSERT(pg->pg_nevents <= PMC_GROUP_MAX_MEMBERS,
+ ("[pmu] probe: gid=%u nevents=%u", pg->pg_id, pg->pg_nevents));
+ pmu_sort_by_weight(pg, order, pg->pg_nevents);
+ used_mask = 0;
+ error = 0;
+ for (i = 0; i < pg->pg_nevents; i++) {
+ error = pmu_assign_one(pg, order[i], p, cpu, &used_mask, true,
+ evictable_rows);
+ if (error != 0)
+ break;
+ }
+ return (error == 0 ? 0 : ENOSPC);
+}
+
+int
+pmu_group_can_fit(pmu_group_t *pg)
+{
+ return (pmu_group_probe(pg, NULL, 0, 0));
+}
+
+int
+pmu_group_can_place(pmu_group_t *pg, struct proc *p, int cpu)
+{
+ if (p == NULL)
+ return (EINVAL);
+ return (pmu_group_probe(pg, p, cpu, 0));
+}
+
+/*
+ * Check if a group can fit once the evictable rows are free.
+ */
+int
+pmu_group_satisfiable(pmu_group_t *pg, struct proc *p, int cpu,
+ uint64_t evictable_rows)
+{
+ if (p == NULL)
+ return (EINVAL);
+ return (pmu_group_probe(pg, p, cpu, evictable_rows));
+}
+
+int
+pmu_assign_group(pmu_group_t *pg, struct proc *p, int cpu)
+{
+ pmu_event_t *order[PMC_GROUP_MAX_MEMBERS];
+ uint32_t used_mask;
+ u_int i;
+ int error;
+
+ if (pg == NULL || p == NULL)
+ return (EINVAL);
+ if (pg->pg_assigned)
+ return (0);
+
+ /* Validate the group configuration before assignment. */
+ error = pmu_validate_group(pg);
+ if (error != 0)
+ return (error);
+
+ KASSERT(pg->pg_nevents <= PMC_GROUP_MAX_MEMBERS,
+ ("[pmu] assign: gid=%u nevents=%u", pg->pg_id, pg->pg_nevents));
+
+ /* Set the CPU index to 0 for virtual mode. */
+ if (cpu < 0)
+ cpu = 0;
+
+ pmu_sort_by_weight(pg, order, pg->pg_nevents);
+
+ used_mask = 0;
+ error = 0;
+ for (i = 0; i < pg->pg_nevents; i++) {
+ error = pmu_assign_one(pg, order[i], p, cpu, &used_mask, false,
+ 0);
+ if (error != 0)
+ break;
+ }
+
+ if (error != 0) {
+ pmu_unassign_group(pg, cpu);
+ return (error);
+ }
+
+ pg->pg_assigned = true;
+ return (0);
+}
+
+int
+pmu_validate_group(pmu_group_t *pg)
+{
+ pmu_event_t *pe;
+ pmc_sched_constraint_t cons;
+ bool sys;
+ u_int nleaders;
+ int lcpu, rc;
+
+ if (pg == NULL || pg->pg_leader == NULL)
+ return (EINVAL);
+ if (pg->pg_nevents == 0)
+ return (EINVAL);
+ nleaders = 0;
+
+ /*
+ * Validate the group mode. Siblings must stay on the leader's side
+ * of the virtual/system split. Counting and sampling may mix within
+ * one side. A system group binds to one CPU.
+ */
+ sys = PMC_IS_SYSTEM_MODE(pg->pg_leader->pe_alloc.pm_mode);
+ lcpu = pg->pg_leader->pe_alloc.pm_cpu;
+
+ /*
+ * PMC_F_DESCENDANTS is leader-only. It governs the whole group. A
+ * system group is bound to a CPU and has no process target, so it
+ * cannot follow a fork.
+ */
+ if (sys && (pg->pg_leader->pe_alloc.pm_flags & PMC_F_DESCENDANTS) !=
+ 0) {
+ PMCDBG1(PMC, OPS, 1,
+ "validate: gid=%u PMC_F_DESCENDANTS on a system group",
+ pg->pg_id);
+ return (EINVAL);
+ }
+
+ TAILQ_FOREACH(pe, &pg->pg_events, pe_sibling) {
+ enum pmc_mode m = pe->pe_alloc.pm_mode;
+
+ if (pe->pe_is_leader) {
+ nleaders++;
+ if (pg->pg_leader != pe)
+ return (EINVAL);
+ } else if ((pe->pe_alloc.pm_flags &
+ (PMC_F_GROUP_MUX | PMC_F_DESCENDANTS)) != 0) {
+ /* Both flags are leader-only. */
+ return (EINVAL);
+ }
+ if (pe->pe_pmc == NULL)
+ return (EINVAL);
+ if (!PMC_ROW_IS_UNASSIGNED(pe->pe_pmc)) {
+ PMCDBG2(PMC, OPS, 1,
+ "validate: gid=%u pm_id=0x%jx already assigned",
+ pg->pg_id, (uintmax_t)pe->pe_pmc->pm_id);
+ return (EINVAL);
+ }
+ if ((pe->pe_alloc.pm_flags & PMC_F_GROUP_DEFER) == 0)
+ return (EINVAL);
+ if (PMC_IS_SYSTEM_MODE(m) != sys) {
+ PMCDBG1(PMC, OPS, 1,
+ "validate: gid=%u mixed system/virtual siblings",
+ pg->pg_id);
+ return (EINVAL);
+ }
+ if (sys) {
+ if (pe->pe_alloc.pm_cpu != lcpu) {
+ PMCDBG3(PMC, OPS, 1,
+ "validate: gid=%u system siblings span "
+ "cpus (%d != %d)", pg->pg_id,
+ pe->pe_alloc.pm_cpu, lcpu);
+ return (EINVAL);
+ }
+ } else if (!PMC_IS_VIRTUAL_MODE(m)) {
+ return (EINVAL);
+ }
+ rc = pmu_event_get_constraint(pe, &cons);
+ if (rc != 0) {
+ PMCDBG2(PMC, OPS, 1,
+ "validate: constraint err=%d class=%d",
+ rc, (int)pe->pe_alloc.pm_class);
+ return (EOPNOTSUPP);
+ }
+ if (cons.pc_allowed_rows == 0)
+ return (EOPNOTSUPP);
+ }
+ if (nleaders != 1)
+ return (EINVAL);
+ return (0);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Oct 19, 1:41 AM (16 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41027798
Default Alt Text
D57632.diff (12 KB)
Attached To
Mode
D57632: hwpmc: add PMU assigner and AMD Zen constraint provider
Attached
Detach File
Event Timeline
Log In to Comment