Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177348064
D61063.1794165725.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
48 KB
Referenced Files
None
Subscribers
None
D61063.1794165725.diff
View Options
Index: tests/sys/pmc/amd/Makefile
===================================================================
--- tests/sys/pmc/amd/Makefile
+++ tests/sys/pmc/amd/Makefile
@@ -1,16 +1,28 @@
TESTSDIR= ${TESTSBASE}/sys/pmc/amd
+ATF_TESTS_C+= pmc_ibs_fetch_op_test
+ATF_TESTS_C+= pmc_ibs_test
+ATF_TESTS_C+= pmc_rowalloc_test
+ATF_TESTS_C+= pmc_start_msr_test
ATF_TESTS_C+= pmu_detect_test
ATF_TESTS_C+= pmu_event_neg_test
ATF_TESTS_C+= pmu_event_test
ATF_TESTS_C+= pmu_mux_test
+ATF_TESTS_C+= pmu_perfmonv2_test
ATF_TESTS_C+= rapl_domains_test
ATF_TESTS_SH+= pmc_frontend_test
+ATF_TESTS_SH+= pmcstat_ibs_test
# These need the counters of cpu0 to themselves.
+TEST_METADATA.pmc_ibs_fetch_op_test+= is_exclusive="true"
+TEST_METADATA.pmc_ibs_test+= is_exclusive="true"
+TEST_METADATA.pmc_rowalloc_test+= is_exclusive="true"
+TEST_METADATA.pmc_start_msr_test+= is_exclusive="true"
TEST_METADATA.pmu_event_neg_test+= is_exclusive="true"
TEST_METADATA.pmu_event_test+= is_exclusive="true"
TEST_METADATA.pmu_mux_test+= is_exclusive="true"
+TEST_METADATA.pmu_perfmonv2_test+= is_exclusive="true"
+TEST_METADATA.pmcstat_ibs_test+= is_exclusive="true"
TEST_METADATA.rapl_domains_test+= is_exclusive="true"
LIBADD+= pmc
Index: tests/sys/pmc/amd/amd_pmc_test_common.h
===================================================================
--- tests/sys/pmc/amd/amd_pmc_test_common.h
+++ tests/sys/pmc/amd/amd_pmc_test_common.h
@@ -22,6 +22,7 @@
#include <pmc.h>
#include <stdbool.h>
#include <stdint.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -268,11 +269,23 @@
}
/*
- * Count the AMD core counter rows ("K8-<n>") on a CPU. The L3, DF and UMC
- * rows share the K8 class and prefix ("K8-L3-", "K8-DF-", "K8-UMC") and are
- * not core counters. With free_only, count only rows that are free.
+ * Return the core counter number of an AMD row name, or -1 for other rows
+ * (L3, DF, UMC). Rows are "AMD-<n>", or "K8-<n>" on older kernels.
*/
static inline int
+amd_test_core_index(const char *name)
+{
+ unsigned int n;
+ char extra;
+
+ if (sscanf(name, "AMD-%u%c", &n, &extra) == 1 ||
+ sscanf(name, "K8-%u%c", &n, &extra) == 1)
+ return ((int)n);
+ return (-1);
+}
+
+/* Count the AMD core counter rows on a CPU; with free_only, the free ones. */
+static inline int
amd_test_count_core_rows(int cpu, bool free_only)
{
struct pmc_pmcinfo *pmcinfo;
@@ -291,8 +304,7 @@
count = 0;
for (i = 0; i < npmc; i++) {
p = &pmcinfo->pm_pmcs[i];
- if (strncmp(p->pm_name, "K8-", 3) != 0 ||
- p->pm_name[3] < '0' || p->pm_name[3] > '9')
+ if (amd_test_core_index(p->pm_name) < 0)
continue;
if (free_only && p->pm_rowdisp != PMC_DISP_FREE)
continue;
Index: tests/sys/pmc/amd/pmc_ibs_fetch_op_test.c
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmc_ibs_fetch_op_test.c
@@ -0,0 +1,337 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ * Author: Davi C.A <davi.chavesazevedo@amd.com>
+ * Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+ */
+
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/pmc.h>
+#include <sys/pmclog.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <dev/hwpmc/hwpmc_ibs.h>
+
+#include <atf-c.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "amd_pmc_test_common.h"
+
+#define FETCH 0x01
+#define OP 0x02
+#define WORK_SECONDS 2
+#define LOG_WAIT 5
+#define MIN_PERCENT 25
+
+struct episode {
+ int error;
+ char what[64];
+ uint64_t samples[2]; /* FETCH, OP */
+ uint64_t dropped;
+ uint64_t processed; /* kern.hwpmc.stats deltas */
+ uint64_t ignored;
+ uint64_t bad; /* foreign PMC, wrong CPU or type */
+};
+
+static void
+fail_step(struct episode *ep, const char *what)
+{
+ if (ep->error == 0) {
+ ep->error = errno != 0 ? errno : EPROTO;
+ strlcpy(ep->what, what, sizeof(ep->what));
+ }
+}
+
+static uint64_t
+stat(const char *name)
+{
+ uint64_t v;
+ size_t len;
+
+ len = sizeof(v);
+ if (sysctlbyname(name, &v, &len, NULL, 0) != 0)
+ return (0);
+ return (v);
+}
+
+/* Keeps both the fetch and the op pipe busy. */
+static void
+workload(void)
+{
+ static volatile uint64_t table[4096];
+ struct timespec end, now;
+ uint64_t v;
+ int i;
+
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ end.tv_sec += WORK_SECONDS;
+ v = 1;
+ do {
+ for (i = 0; i < 100000; i++) {
+ v = v * 6364136223846793005ULL + 1442695040888963407ULL;
+ if ((v >> 61) & 1)
+ table[v & (nitems(table) - 1)] += v;
+ else
+ v ^= table[(v >> 17) & (nitems(table) - 1)];
+ }
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ } while (timespeccmp(&now, &end, <));
+}
+
+static void
+drain(void *parser, int kq, const pmc_id_t *ids, int cpu, struct episode *ep)
+{
+ static const uint8_t type[2] = {
+ PMC_CC_MULTIPART_IBS_FETCH, PMC_CC_MULTIPART_IBS_OP
+ };
+ struct pmclog_ev ev;
+ struct kevent ke;
+ struct timespec deadline, left, now;
+ const struct pmclog_ev_callchain *cc;
+ int k;
+
+ clock_gettime(CLOCK_MONOTONIC, &deadline);
+ deadline.tv_sec += LOG_WAIT;
+ for (;;) {
+ memset(&ev, 0, sizeof(ev));
+ if (pmclog_read(parser, &ev) == 0) {
+ if (ev.pl_type == PMCLOG_TYPE_DROPNOTIFY)
+ ep->dropped++;
+ if (ev.pl_type != PMCLOG_TYPE_CALLCHAIN)
+ continue;
+ cc = &ev.pl_u.pl_cc;
+ for (k = 0; k < 2; k++)
+ if (ids[k] != PMC_ID_INVALID &&
+ cc->pl_pmcid == ids[k])
+ break;
+ if (k == 2 || cc->pl_npc == 0 ||
+ PMC_CALLCHAIN_CPUFLAGS_TO_CPU(cc->pl_cpuflags) !=
+ (unsigned int)cpu ||
+ ((const uint8_t *)cc->pl_pc)[0] != type[k])
+ ep->bad++;
+ else
+ ep->samples[k]++;
+ continue;
+ }
+ if (ev.pl_state != PMCLOG_EOF) {
+ errno = EPROTO;
+ fail_step(ep, "pmclog_read");
+ return;
+ }
+ if (ev.pl_type == PMCLOG_TYPE_CLOSELOG)
+ return;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (timespeccmp(&now, &deadline, >=)) {
+ errno = ETIMEDOUT;
+ fail_step(ep, "waiting for CLOSELOG");
+ return;
+ }
+ timespecsub(&deadline, &now, &left);
+ (void)kevent(kq, NULL, 0, &ke, 1, &left);
+ }
+}
+
+/* A closed log leaves shutdown state on its owner: use a fresh process. */
+static void
+run_episode(int cpu, int engines, struct episode *ep)
+{
+ static const char *const event[2] = { "IBS-FETCH", "IBS-OP" };
+ static const pmc_value_t rate[2] = {
+ IBS_FETCH_MIN_RATE, IBS_OP_MIN_RATE
+ };
+ char path[] = "ibs-fo.XXXXXX";
+ struct kevent ke;
+ cpuset_t set;
+ pmc_id_t ids[2] = { PMC_ID_INVALID, PMC_ID_INVALID };
+ void *parser;
+ int fd, k, kq, reader;
+
+ memset(ep, 0, sizeof(*ep));
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ if (pmc_init() != 0 || cpuset_setaffinity(CPU_LEVEL_WHICH,
+ CPU_WHICH_TID, -1, sizeof(set), &set) != 0) {
+ fail_step(ep, "pmc_init or pinning");
+ return;
+ }
+ if ((fd = mkstemp(path)) < 0 ||
+ (reader = open(path, O_RDONLY)) < 0 || unlink(path) != 0 ||
+ (parser = pmclog_open(reader)) == NULL ||
+ (kq = kqueue()) < 0) {
+ fail_step(ep, "log setup");
+ return;
+ }
+ EV_SET(&ke, reader, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+ NOTE_WRITE | NOTE_EXTEND, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) != 0 ||
+ pmc_configure_logfile(fd) != 0) {
+ fail_step(ep, "pmc_configure_logfile");
+ return;
+ }
+
+ ep->processed = stat("kern.hwpmc.stats.intr_processed");
+ ep->ignored = stat("kern.hwpmc.stats.intr_ignored");
+ for (k = 0; k < 2; k++)
+ if ((engines & (1 << k)) != 0 && (pmc_allocate(event[k],
+ PMC_MODE_SS, 0, cpu, &ids[k], rate[k]) != 0 ||
+ pmc_start(ids[k]) != 0))
+ fail_step(ep, event[k]);
+ if (ep->error == 0)
+ workload();
+ for (k = 0; k < 2; k++) {
+ if (ids[k] == PMC_ID_INVALID)
+ continue;
+ (void)pmc_stop(ids[k]);
+ if (pmc_release(ids[k]) != 0)
+ fail_step(ep, "pmc_release");
+ }
+ if (pmc_flush_logfile() != 0 || pmc_close_logfile() != 0)
+ fail_step(ep, "closing the log");
+ else
+ drain(parser, kq, ids, cpu, ep);
+ ep->processed = stat("kern.hwpmc.stats.intr_processed") - ep->processed;
+ ep->ignored = stat("kern.hwpmc.stats.intr_ignored") - ep->ignored;
+ (void)pmc_configure_logfile(-1);
+}
+
+static void
+episode(int cpu, int engines, struct episode *ep)
+{
+ pid_t child;
+ ssize_t n;
+ int fds[2], status;
+
+ ATF_REQUIRE(pipe(fds) == 0);
+ child = fork();
+ ATF_REQUIRE(child >= 0);
+ if (child == 0) {
+ close(fds[0]);
+ run_episode(cpu, engines, ep);
+ n = write(fds[1], ep, sizeof(*ep));
+ _exit(n == (ssize_t)sizeof(*ep) ? 0 : 1);
+ }
+ close(fds[1]);
+ n = read(fds[0], ep, sizeof(*ep));
+ close(fds[0]);
+ ATF_REQUIRE(waitpid(child, &status, 0) == child);
+ ATF_REQUIRE_MSG(n == (ssize_t)sizeof(*ep) && WIFEXITED(status) &&
+ WEXITSTATUS(status) == 0, "episode owner failed: status %#x",
+ status);
+ printf("engines %#x: fetch %ju op %ju dropped %ju bad %ju, "
+ "interrupts %ju processed %ju ignored\n", engines,
+ (uintmax_t)ep->samples[0], (uintmax_t)ep->samples[1],
+ (uintmax_t)ep->dropped, (uintmax_t)ep->bad,
+ (uintmax_t)ep->processed, (uintmax_t)ep->ignored);
+ ATF_REQUIRE_MSG(ep->error == 0, "%s failed: %s", ep->what,
+ strerror(ep->error));
+ ATF_REQUIRE_MSG(ep->dropped == 0 && ep->bad == 0,
+ "%ju dropped buffers, %ju malformed or misrouted samples",
+ (uintmax_t)ep->dropped, (uintmax_t)ep->bad);
+}
+
+/* Skip unless IBS Fetch and Op sampling work; return the CPU to use. */
+static int
+setup(void)
+{
+ cpuset_t mask;
+ u_int regs[4];
+ int cpu;
+
+ amd_test_skip_unless_amd();
+ do_cpuid(0x80000000, regs);
+ if (regs[0] < CPUID_IBSID)
+ atf_tc_skip("CPU lacks the IBS CPUID leaf");
+ do_cpuid(CPUID_IBSID, regs);
+ if ((regs[0] & (CPUID_IBSID_FETCHSAM | CPUID_IBSID_OPSAM)) !=
+ (CPUID_IBSID_FETCHSAM | CPUID_IBSID_OPSAM))
+ atf_tc_skip("CPU lacks IBS Fetch or Op sampling");
+ amd_test_skip_unless_hwpmc();
+
+ ATF_REQUIRE(cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(mask), &mask) == 0);
+ cpu = CPU_FFS(&mask) - 1;
+ ATF_REQUIRE(cpu >= 0);
+ return (cpu);
+}
+
+/*
+ * Regression test for 572cd669d6: pmc_ibs_intr() skipped a valid Op sample
+ * when Fetch was valid in the same NMI, and IBS-OP starved.
+ */
+ATF_TC(fetch_and_op_together);
+ATF_TC_HEAD(fetch_and_op_together, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "IBS-FETCH and IBS-OP system sampling on one CPU each deliver at "
+ "least a quarter of the samples they deliver alone");
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "60");
+}
+ATF_TC_BODY(fetch_and_op_together, tc)
+{
+ struct episode both, fetch, op;
+ int cpu;
+
+ cpu = setup();
+
+ episode(cpu, FETCH, &fetch);
+ episode(cpu, OP, &op);
+ ATF_REQUIRE_MSG(fetch.samples[0] > 0 && op.samples[1] > 0,
+ "an engine alone delivered no samples: fetch %ju, op %ju",
+ (uintmax_t)fetch.samples[0], (uintmax_t)op.samples[1]);
+ episode(cpu, FETCH | OP, &both);
+
+ ATF_CHECK_MSG(both.samples[0] * 100 >= fetch.samples[0] * MIN_PERCENT,
+ "IBS-FETCH starved beside IBS-OP: %ju samples, %ju alone",
+ (uintmax_t)both.samples[0], (uintmax_t)fetch.samples[0]);
+ ATF_CHECK_MSG(both.samples[1] * 100 >= op.samples[1] * MIN_PERCENT,
+ "IBS-OP starved beside IBS-FETCH: %ju samples, %ju alone",
+ (uintmax_t)both.samples[1], (uintmax_t)op.samples[1]);
+}
+
+/* c97cea955f: an IBS interrupt was counted as processed twice. */
+ATF_TC(interrupts_counted_once);
+ATF_TC_HEAD(interrupts_counted_once, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Each IBS-FETCH sampling interrupt is counted once in "
+ "kern.hwpmc.stats.intr_processed and none in intr_ignored");
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(interrupts_counted_once, tc)
+{
+ struct episode ep;
+
+ episode(setup(), FETCH, &ep);
+ ATF_CHECK_MSG(ep.ignored == 0, "%ju interrupts counted as ignored",
+ (uintmax_t)ep.ignored);
+ ATF_CHECK_MSG(ep.processed >= ep.samples[0] &&
+ ep.processed <= ep.samples[0] + ep.samples[0] / 10 + 16,
+ "%ju interrupts processed for %ju samples",
+ (uintmax_t)ep.processed, (uintmax_t)ep.samples[0]);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, fetch_and_op_together);
+ ATF_TP_ADD_TC(tp, interrupts_counted_once);
+
+ return (atf_no_error());
+}
Index: tests/sys/pmc/amd/pmc_ibs_test.c
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmc_ibs_test.c
@@ -0,0 +1,501 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ * Author: Davi C.A <davi.chavesazevedo@amd.com>
+ * Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+ */
+
+#include <sys/param.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/pmc.h>
+#include <sys/pmclog.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <dev/hwpmc/hwpmc_ibs.h>
+
+#include <atf-c.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pmc.h>
+#include <pmclog.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "amd_pmc_test_common.h"
+
+#define IBS_RATE IBS_FETCH_MIN_RATE
+#define IBS_CAPS (PMC_CAP_INTERRUPT | PMC_CAP_SYSTEM | \
+ PMC_CAP_EDGE | PMC_CAP_QUALIFIER | \
+ PMC_CAP_PRECISE)
+#define LOG_WAIT_SECONDS 5
+
+static const struct pmc_classinfo *
+ibs_classinfo(void)
+{
+ const struct pmc_cpuinfo *cpuinfo;
+
+ ATF_REQUIRE_MSG(pmc_cpuinfo(&cpuinfo) == 0,
+ "pmc_cpuinfo() failed: %s", strerror(errno));
+ return (amd_test_find_classinfo(cpuinfo, PMC_CLASS_IBS));
+}
+
+static void
+skip_unless_ibs(void)
+{
+ u_int regs[4];
+
+ amd_test_skip_unless_amd();
+ do_cpuid(0x80000000, regs);
+ if (regs[0] < CPUID_IBSID)
+ atf_tc_skip("CPU lacks the IBS CPUID leaf");
+ do_cpuid(CPUID_IBSID, regs);
+ if ((regs[0] & (CPUID_IBSID_IBSFFV | CPUID_IBSID_FETCHSAM |
+ CPUID_IBSID_OPSAM)) != (CPUID_IBSID_IBSFFV |
+ CPUID_IBSID_FETCHSAM | CPUID_IBSID_OPSAM))
+ atf_tc_skip("CPU lacks IBS Fetch or Op sampling");
+ amd_test_skip_unless_hwpmc();
+ if (ibs_classinfo() == NULL)
+ atf_tc_skip("hwpmc does not expose PMC_CLASS_IBS");
+}
+
+static int
+pin_cpu(void)
+{
+ cpuset_t mask;
+ int cpu;
+
+ ATF_REQUIRE(cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(mask), &mask) == 0);
+ cpu = CPU_FFS(&mask) - 1;
+ ATF_REQUIRE(cpu >= 0);
+ CPU_ZERO(&mask);
+ CPU_SET(cpu, &mask);
+ ATF_REQUIRE_MSG(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(mask), &mask) == 0, "cannot bind to cpu%d: %s", cpu,
+ strerror(errno));
+ return (cpu);
+}
+
+static int
+alloc_errno(const char *event, enum pmc_mode mode, int cpu,
+ pmc_value_t rate)
+{
+ pmc_id_t id;
+
+ id = PMC_ID_INVALID;
+ errno = 0;
+ if (pmc_allocate(event, mode, 0, cpu, &id, rate) == 0) {
+ amd_test_release_pmc(id);
+ return (0);
+ }
+ return (errno);
+}
+
+static pmc_id_t
+alloc_attach_fetch(pid_t pid, pmc_value_t rate)
+{
+ pmc_id_t id;
+ int error;
+
+ id = PMC_ID_INVALID;
+ ATF_REQUIRE_MSG(pmc_allocate("IBS-FETCH", PMC_MODE_TS, 0, PMC_CPU_ANY,
+ &id, rate) == 0, "pmc_allocate(TS IBS-FETCH) failed: %s",
+ strerror(errno));
+ if (pmc_attach(id, pid) != 0) {
+ error = errno;
+ amd_test_release_pmc(id);
+ atf_tc_fail("pmc_attach(IBS-FETCH, %d) failed: %s", pid,
+ strerror(error));
+ }
+ return (id);
+}
+
+ATF_TC(class_info);
+ATF_TC_HEAD(class_info, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmc_cpuinfo() reports PMC_CLASS_IBS with two PMCs, width 0 and "
+ "the interrupt, system, edge, qualifier and precise capabilities");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(class_info, tc)
+{
+ const struct pmc_classinfo *ci;
+
+ skip_unless_ibs();
+ ci = ibs_classinfo();
+ ATF_CHECK_EQ_MSG(ci->pm_num, 2, "IBS class has %u PMCs", ci->pm_num);
+ ATF_CHECK_EQ_MSG(ci->pm_width, 0, "IBS class width is %u",
+ ci->pm_width);
+ ATF_CHECK_MSG((ci->pm_caps & IBS_CAPS) == IBS_CAPS,
+ "IBS class caps %#x lack %#x", ci->pm_caps, IBS_CAPS);
+}
+
+ATF_TC(class_and_event_names);
+ATF_TC_HEAD(class_and_event_names, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "libpmc names the class IBS and lists the FETCH and OP events");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(class_and_event_names, tc)
+{
+ const char **names;
+ const char *name;
+ bool fetch, op;
+ int i, n;
+
+ skip_unless_ibs();
+ name = pmc_name_of_class(PMC_CLASS_IBS);
+ ATF_REQUIRE(name != NULL);
+ ATF_CHECK_STREQ(name, "IBS");
+
+ ATF_REQUIRE_MSG(pmc_event_names_of_class(PMC_CLASS_IBS, &names,
+ &n) == 0, "pmc_event_names_of_class() failed: %s",
+ strerror(errno));
+ fetch = op = false;
+ for (i = 0; i < n; i++) {
+ fetch |= strcasecmp(names[i], "FETCH") == 0;
+ op |= strcasecmp(names[i], "OP") == 0;
+ }
+ free(names);
+ ATF_CHECK_MSG(fetch && op, "IBS events: FETCH %s, OP %s",
+ fetch ? "listed" : "missing", op ? "listed" : "missing");
+}
+
+ATF_TC(pmcinfo_rows);
+ATF_TC_HEAD(pmcinfo_rows, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmc_pmcinfo() has exactly one IBS-FETCH and one IBS-OP row");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(pmcinfo_rows, tc)
+{
+ size_t fetch, op;
+ int cpu;
+
+ skip_unless_ibs();
+ cpu = pin_cpu();
+ fetch = amd_test_count_pmc_rows_with_prefix(cpu, "IBS-FETCH");
+ op = amd_test_count_pmc_rows_with_prefix(cpu, "IBS-OP");
+ ATF_CHECK_MSG(fetch == 1 && op == 1,
+ "cpu%d has %zu IBS-FETCH and %zu IBS-OP rows", cpu, fetch, op);
+}
+
+ATF_TC(alloc_system_sampling);
+ATF_TC_HEAD(alloc_system_sampling, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "IBS-FETCH and IBS-OP allocate as system-sampling PMCs; IBS-FETCH "
+ "reports the IBS capabilities and width 0");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(alloc_system_sampling, tc)
+{
+ pmc_id_t fetch, op;
+ uint32_t caps, width;
+ int cpu;
+
+ skip_unless_ibs();
+ cpu = pin_cpu();
+ fetch = op = PMC_ID_INVALID;
+ ATF_REQUIRE_MSG(pmc_allocate("IBS-FETCH", PMC_MODE_SS, 0, cpu, &fetch,
+ IBS_RATE) == 0, "pmc_allocate(IBS-FETCH) failed: %s",
+ strerror(errno));
+ ATF_CHECK_MSG(pmc_allocate("IBS-OP", PMC_MODE_SS, 0, cpu, &op,
+ IBS_RATE) == 0, "pmc_allocate(IBS-OP) failed: %s",
+ strerror(errno));
+
+ caps = 0;
+ width = UINT32_MAX;
+ ATF_CHECK(pmc_capabilities(fetch, &caps) == 0);
+ ATF_CHECK(pmc_width(fetch, &width) == 0);
+ ATF_CHECK_MSG((caps & IBS_CAPS) == IBS_CAPS,
+ "IBS-FETCH caps %#x lack %#x", caps, IBS_CAPS);
+ ATF_CHECK_EQ_MSG(width, 0, "IBS-FETCH width is %u", width);
+
+ amd_test_release_pmc(op);
+ amd_test_release_pmc(fetch);
+}
+
+ATF_TC(alloc_refusals);
+ATF_TC_HEAD(alloc_refusals, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "IBS allocation fails with EINVAL in counting mode, below the "
+ "minimum sample rate and with an unknown qualifier");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(alloc_refusals, tc)
+{
+ int cpu, error;
+
+ skip_unless_ibs();
+ cpu = pin_cpu();
+
+ error = alloc_errno("IBS-FETCH", PMC_MODE_SC, cpu, IBS_RATE);
+ ATF_CHECK_MSG(error == EINVAL, "counting mode: errno %d (%s)", error,
+ strerror(error));
+ error = alloc_errno("IBS-OP", PMC_MODE_SS, cpu, IBS_OP_MIN_RATE - 1);
+ ATF_CHECK_MSG(error == EINVAL, "rate %d: errno %d (%s)",
+ IBS_OP_MIN_RATE - 1, error, strerror(error));
+ error = alloc_errno("IBS-OP,no_such_qualifier", PMC_MODE_SS, cpu,
+ IBS_RATE);
+ ATF_CHECK_MSG(error == EINVAL, "unknown qualifier: errno %d (%s)",
+ error, strerror(error));
+}
+
+ATF_TC(fetch_attach_detach);
+ATF_TC_HEAD(fetch_attach_detach, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "A process-mode IBS-FETCH PMC attaches to its owner, detaches and "
+ "is released");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(fetch_attach_detach, tc)
+{
+ pmc_id_t id;
+
+ skip_unless_ibs();
+ (void)pin_cpu();
+ id = alloc_attach_fetch(getpid(), IBS_RATE);
+ ATF_CHECK_MSG(pmc_detach(id, getpid()) == 0,
+ "pmc_detach() failed: %s", strerror(errno));
+ amd_test_release_pmc(id);
+}
+
+ATF_TC(fetch_self_count);
+ATF_TC_HEAD(fetch_self_count, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "A running IBS-FETCH PMC attached to its owner reads a non-zero "
+ "count while the owner makes system calls");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(fetch_self_count, tc)
+{
+ pmc_id_t id;
+ pmc_value_t v;
+ int i, round;
+
+ skip_unless_ibs();
+ (void)pin_cpu();
+ id = alloc_attach_fetch(getpid(), IBS_RATE);
+ ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start() failed: %s",
+ strerror(errno));
+
+ /* The count restarts after each sample, so one read may see 0. */
+ v = 0;
+ for (round = 0; round < 100 && v == 0; round++) {
+ for (i = 0; i < 10000; i++)
+ (void)getppid();
+ ATF_REQUIRE(pmc_read(id, &v) == 0);
+ }
+ ATF_CHECK(pmc_stop(id) == 0);
+ ATF_CHECK(pmc_detach(id, getpid()) == 0);
+ amd_test_release_pmc(id);
+ ATF_CHECK_MSG(v != 0, "IBS-FETCH count stayed 0");
+}
+
+/* Wait for a sample of pid, or for CLOSELOG if closing. */
+static int
+wait_log(void *parser, int kq, pmc_id_t id, pid_t pid, bool closing,
+ bool *sampled, bool *dropped)
+{
+ struct pmclog_ev ev;
+ struct kevent ke;
+ struct timespec deadline, left, now;
+ const struct pmclog_ev_callchain *cc;
+ const uint8_t *hdr;
+
+ ATF_REQUIRE(clock_gettime(CLOCK_MONOTONIC, &deadline) == 0);
+ deadline.tv_sec += LOG_WAIT_SECONDS;
+ for (;;) {
+ if (!closing && pmc_flush_logfile() != 0)
+ return (errno);
+ memset(&ev, 0, sizeof(ev));
+ if (pmclog_read(parser, &ev) == 0) {
+ if (ev.pl_type == PMCLOG_TYPE_DROPNOTIFY)
+ *dropped = true;
+ if (ev.pl_type != PMCLOG_TYPE_CALLCHAIN)
+ continue;
+ cc = &ev.pl_u.pl_cc;
+ hdr = (const uint8_t *)cc->pl_pc;
+ if (cc->pl_pmcid != id || cc->pl_pid != (uint32_t)pid ||
+ (cc->pl_cpuflags & PMC_CC_F_MULTIPART) == 0 ||
+ cc->pl_npc == 0 ||
+ hdr[0] != PMC_CC_MULTIPART_IBS_FETCH)
+ return (EPROTO);
+ *sampled = true;
+ if (!closing)
+ return (0);
+ continue;
+ }
+ if (ev.pl_state != PMCLOG_EOF)
+ return (EPROTO);
+ if (ev.pl_type == PMCLOG_TYPE_CLOSELOG)
+ return (closing ? 0 : EPROTO);
+ ATF_REQUIRE(clock_gettime(CLOCK_MONOTONIC, &now) == 0);
+ if (timespeccmp(&now, &deadline, >=))
+ return (ETIMEDOUT);
+ timespecsub(&deadline, &now, &left);
+ if (kevent(kq, NULL, 0, &ke, 1, &left) < 0 && errno != EINTR)
+ return (errno);
+ }
+}
+
+ATF_TC(fetch_child_samples);
+ATF_TC_HEAD(fetch_child_samples, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "An IBS-FETCH PMC attached to a child writes IBS-FETCH samples of "
+ "that child to the owner's log, without drops");
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+ATF_TC_BODY(fetch_child_samples, tc)
+{
+ struct kevent ke;
+ char path[] = "ibs-fetch.XXXXXX";
+ volatile uint64_t x;
+ uint64_t i;
+ pmc_id_t id;
+ pid_t child;
+ const char *what;
+ void *parser;
+ bool configured, dropped, sampled;
+ int error, fd, kq, reader, status;
+
+ skip_unless_ibs();
+ (void)pin_cpu();
+
+ fd = mkstemp(path);
+ ATF_REQUIRE(fd >= 0);
+ reader = open(path, O_RDONLY);
+ ATF_REQUIRE(reader >= 0);
+ ATF_REQUIRE(unlink(path) == 0);
+ parser = pmclog_open(reader);
+ ATF_REQUIRE(parser != NULL);
+ kq = kqueue();
+ ATF_REQUIRE(kq >= 0);
+ EV_SET(&ke, reader, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+ NOTE_WRITE | NOTE_EXTEND, 0, NULL);
+ ATF_REQUIRE(kevent(kq, &ke, 1, NULL, 0, NULL) == 0);
+
+ child = fork();
+ ATF_REQUIRE(child >= 0);
+ if (child == 0) {
+ if (raise(SIGSTOP) != 0)
+ _exit(1);
+ x = 1;
+ for (i = 0; i < 500000000; i++)
+ x = x * 1664525U + 1013904223U;
+ _exit(0);
+ }
+ /* From here on, kill and reap the child before any failure. */
+ what = NULL;
+ configured = false;
+ id = PMC_ID_INVALID;
+ if (waitpid(child, &status, WUNTRACED) != child || !WIFSTOPPED(status))
+ what = "stopping the child";
+ else if (pmc_configure_logfile(fd) != 0)
+ what = "pmc_configure_logfile";
+ else {
+ configured = true;
+ if (pmc_allocate("IBS-FETCH", PMC_MODE_TS, 0, PMC_CPU_ANY, &id,
+ IBS_FETCH_MAX_RATE) != 0)
+ what = "pmc_allocate";
+ else if (pmc_attach(id, child) != 0)
+ what = "pmc_attach";
+ else if (pmc_start(id) != 0)
+ what = "pmc_start";
+ else if (kill(child, SIGCONT) != 0)
+ what = "kill(SIGCONT)";
+ }
+ error = what != NULL ? errno : 0;
+
+ sampled = dropped = false;
+ if (error == 0)
+ error = wait_log(parser, kq, id, child, false, &sampled,
+ &dropped);
+
+ (void)kill(child, SIGKILL);
+ (void)waitpid(child, &status, 0);
+ if (id != PMC_ID_INVALID) {
+ (void)pmc_stop(id);
+ amd_test_release_pmc(id);
+ }
+ if (configured) {
+ ATF_CHECK(pmc_flush_logfile() == 0);
+ if (pmc_close_logfile() != 0)
+ ATF_CHECK_MSG(false, "pmc_close_logfile() failed: %s",
+ strerror(errno));
+ else if (error == 0)
+ error = wait_log(parser, kq, id, child, true,
+ &sampled, &dropped);
+ ATF_CHECK(pmc_configure_logfile(-1) == 0);
+ }
+ pmclog_close(parser);
+ close(kq);
+ close(reader);
+ close(fd);
+
+ ATF_REQUIRE_MSG(what == NULL, "%s failed: %s", what, strerror(error));
+ ATF_REQUIRE_MSG(error == 0, "reading the log failed: %s",
+ strerror(error));
+ ATF_CHECK_MSG(sampled && !dropped, "IBS-FETCH log: sampled %d, "
+ "dropped %d", sampled, dropped);
+}
+
+ATF_TC(getmsr_rejected);
+ATF_TC_HEAD(getmsr_rejected, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmc_get_msr() fails with ENOSYS or EINVAL for an IBS-FETCH PMC");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(getmsr_rejected, tc)
+{
+ pmc_id_t id;
+ uint32_t msr;
+ int error, rc;
+
+ skip_unless_ibs();
+ (void)pin_cpu();
+ id = alloc_attach_fetch(getpid(), IBS_RATE);
+ errno = 0;
+ rc = pmc_get_msr(id, &msr);
+ error = errno;
+ ATF_CHECK(pmc_detach(id, getpid()) == 0);
+ amd_test_release_pmc(id);
+ /* IBS has no GETMSR method; the sampling-mode check may come first. */
+ ATF_CHECK_MSG(rc == -1 && (error == ENOSYS || error == EINVAL),
+ "pmc_get_msr() returned %d, errno %d (%s)", rc, error,
+ strerror(error));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, class_info);
+ ATF_TP_ADD_TC(tp, class_and_event_names);
+ ATF_TP_ADD_TC(tp, pmcinfo_rows);
+ ATF_TP_ADD_TC(tp, alloc_system_sampling);
+ ATF_TP_ADD_TC(tp, alloc_refusals);
+ ATF_TP_ADD_TC(tp, fetch_attach_detach);
+ ATF_TP_ADD_TC(tp, fetch_self_count);
+ ATF_TP_ADD_TC(tp, fetch_child_samples);
+ ATF_TP_ADD_TC(tp, getmsr_rejected);
+
+ return (atf_no_error());
+}
Index: tests/sys/pmc/amd/pmc_rowalloc_test.c
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmc_rowalloc_test.c
@@ -0,0 +1,285 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ * Author: Davi C.A <davi.chavesazevedo@amd.com>
+ * Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+ */
+
+#include <sys/param.h>
+#include <sys/pmc.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+
+#include <errno.h>
+#include <pmc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "amd_pmc_test_common.h"
+
+#define CORE_EVENT "unhalted-cycles"
+
+struct row_snapshot {
+ int ncpu;
+ int npmc;
+ enum pmc_disp *disp;
+};
+
+static void
+snapshot_take(struct row_snapshot *snap)
+{
+ struct pmc_pmcinfo *pmcinfo;
+ int cpu, i;
+
+ snap->ncpu = pmc_ncpu();
+ snap->npmc = pmc_npmc(0);
+ ATF_REQUIRE_MSG(snap->ncpu > 0 && snap->npmc > 0,
+ "pmc_ncpu/pmc_npmc failed: %s", strerror(errno));
+ snap->disp = calloc((size_t)snap->ncpu * snap->npmc,
+ sizeof(*snap->disp));
+ ATF_REQUIRE(snap->disp != NULL);
+ for (cpu = 0; cpu < snap->ncpu; cpu++) {
+ /* CPUs that are absent or halted have no PMC state. */
+ if (pmc_pmcinfo(cpu, &pmcinfo) != 0)
+ continue;
+ for (i = 0; i < snap->npmc; i++)
+ snap->disp[cpu * snap->npmc + i] =
+ pmcinfo->pm_pmcs[i].pm_rowdisp;
+ free(pmcinfo);
+ }
+}
+
+static void
+snapshot_require_equal(struct row_snapshot *a, struct row_snapshot *b,
+ const char *what)
+{
+ size_t i, n;
+
+ ATF_REQUIRE(a->ncpu == b->ncpu && a->npmc == b->npmc);
+ n = (size_t)a->ncpu * a->npmc;
+ for (i = 0; i < n; i++)
+ ATF_CHECK_MSG(a->disp[i] == b->disp[i],
+ "%s changed cpu%zu row %zu from %s to %s", what,
+ i / a->npmc, i % a->npmc,
+ pmc_name_of_disposition(a->disp[i]),
+ pmc_name_of_disposition(b->disp[i]));
+}
+
+static void
+snapshot_free(struct row_snapshot *snap)
+{
+ free(snap->disp);
+ snap->disp = NULL;
+}
+
+static void
+skip_unless_ready(void)
+{
+ struct amd_test_cpu cpu;
+
+ amd_test_skip_unless_zen(&cpu);
+ amd_test_skip_unless_hwpmc();
+ if (!pmc_pmu_enabled())
+ atf_tc_skip("pmu-events not enabled");
+}
+
+static int
+alloc_tc(pmc_id_t *id)
+{
+ *id = PMC_ID_INVALID;
+ return (pmc_allocate(CORE_EVENT, PMC_MODE_TC, 0, PMC_CPU_ANY, id,
+ 0) == 0 ? 0 : errno);
+}
+
+static size_t
+fill_rows(pmc_id_t *ids, size_t max)
+{
+ size_t n;
+ int error;
+
+ for (n = 0; n < max; n++) {
+ error = alloc_tc(&ids[n]);
+ if (error == 0)
+ continue;
+ ATF_REQUIRE_MSG(n > 0 && error == EINVAL,
+ "allocation %zu failed with %d (%s), expected EINVAL "
+ "after at least one success", n, error, strerror(error));
+ return (n);
+ }
+ atf_tc_fail("hwpmc granted %zu process-mode PMCs without refusing one",
+ max);
+}
+
+static void
+release_all(pmc_id_t *ids, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ if (ids[i] != PMC_ID_INVALID)
+ amd_test_release_pmc(ids[i]);
+}
+
+ATF_TC(alloc_reserves_distinct_rows);
+ATF_TC_HEAD(alloc_reserves_distinct_rows, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Two process-mode core PMCs get two distinct rows, both reported "
+ "as THREAD");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(alloc_reserves_distinct_rows, tc)
+{
+ struct row_snapshot snap;
+ pmc_id_t id[2];
+ unsigned int r0, r1;
+ int error, i;
+
+ skip_unless_ready();
+ for (i = 0; i < 2; i++) {
+ error = alloc_tc(&id[i]);
+ ATF_REQUIRE_MSG(error == 0, "allocation %d failed: %s", i,
+ strerror(error));
+ }
+
+ r0 = PMC_ID_TO_ROWINDEX(id[0]);
+ r1 = PMC_ID_TO_ROWINDEX(id[1]);
+ ATF_CHECK_MSG(r0 != r1, "both PMCs got row %u", r0);
+ snapshot_take(&snap);
+ ATF_CHECK_MSG(snap.disp[r0] == PMC_DISP_THREAD &&
+ snap.disp[r1] == PMC_DISP_THREAD,
+ "rows %u and %u are %s and %s, expected THREAD", r0, r1,
+ pmc_name_of_disposition(snap.disp[r0]),
+ pmc_name_of_disposition(snap.disp[r1]));
+ snapshot_free(&snap);
+
+ release_all(id, nitems(id));
+}
+
+ATF_TC(failed_alloc_leaves_no_trace);
+ATF_TC_HEAD(failed_alloc_leaves_no_trace, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Once the core rows are full, a refused allocation leaves every "
+ "row disposition unchanged, and releasing restores the start "
+ "state");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(failed_alloc_leaves_no_trace, tc)
+{
+ struct row_snapshot after, before, full, initial;
+ pmc_id_t extra, *ids;
+ size_t max, n;
+ int error;
+
+ skip_unless_ready();
+ max = (size_t)pmc_npmc(0) + 1;
+ ids = calloc(max, sizeof(*ids));
+ ATF_REQUIRE(ids != NULL);
+
+ snapshot_take(&initial);
+ n = fill_rows(ids, max);
+ snapshot_take(&full);
+ error = alloc_tc(&extra);
+ if (error == 0)
+ amd_test_release_pmc(extra);
+ ATF_CHECK_MSG(error == EINVAL,
+ "an allocation beyond %zu rows returned %d (%s), expected EINVAL",
+ n, error, strerror(error));
+ snapshot_take(&before);
+ snapshot_require_equal(&full, &before, "a refused allocation");
+
+ release_all(ids, n);
+ snapshot_take(&after);
+ snapshot_require_equal(&initial, &after, "allocate and release");
+
+ snapshot_free(&after);
+ snapshot_free(&before);
+ snapshot_free(&full);
+ snapshot_free(&initial);
+ free(ids);
+}
+
+ATF_TC(thread_rows_refuse_system_pmc);
+ATF_TC_HEAD(thread_rows_refuse_system_pmc, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "With every core row held by process-mode PMCs of another "
+ "process, a system-mode core PMC is refused with EINVAL and no "
+ "row disposition changes");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(thread_rows_refuse_system_pmc, tc)
+{
+ struct row_snapshot after, full;
+ pmc_id_t id, sys;
+ pid_t child;
+ size_t max, n;
+ int error, hold[2], report[2], status;
+ char c;
+
+ skip_unless_ready();
+ max = (size_t)pmc_npmc(0) + 1;
+ ATF_REQUIRE(pipe(hold) == 0);
+ ATF_REQUIRE(pipe(report) == 0);
+ child = fork();
+ ATF_REQUIRE(child >= 0);
+ if (child == 0) {
+ /* Another owner holds the rows, not the parent. */
+ close(hold[1]);
+ close(report[0]);
+ error = 0;
+ for (n = 0; n < max && (error = alloc_tc(&id)) == 0; n++)
+ ;
+ c = n > 0 && error == EINVAL ? 'f' : 'e';
+ (void)write(report[1], &c, 1);
+ (void)read(hold[0], &c, 1);
+ _exit(0);
+ }
+ close(hold[0]);
+ close(report[1]);
+ if (read(report[0], &c, 1) != 1 || c != 'f') {
+ close(hold[1]);
+ (void)waitpid(child, NULL, 0);
+ atf_tc_fail("the child could not fill the core rows");
+ }
+
+ snapshot_take(&full);
+ sys = PMC_ID_INVALID;
+ error = pmc_allocate(CORE_EVENT, PMC_MODE_SC, 0, 0, &sys, 0) == 0 ?
+ 0 : errno;
+ if (error == 0)
+ amd_test_release_pmc(sys);
+ snapshot_take(&after);
+ close(hold[1]);
+ close(report[0]);
+ ATF_REQUIRE(waitpid(child, &status, 0) == child);
+
+ ATF_CHECK_MSG(error == EINVAL,
+ "system-mode allocation returned %d (%s), expected EINVAL", error,
+ strerror(error));
+ snapshot_require_equal(&full, &after, "a refused system allocation");
+ ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "child failed: status %#x", status);
+ snapshot_free(&after);
+ snapshot_free(&full);
+
+ /* Positive control: the rows are free again. */
+ error = pmc_allocate(CORE_EVENT, PMC_MODE_SC, 0, 0, &sys, 0) == 0 ?
+ 0 : errno;
+ ATF_REQUIRE_MSG(error == 0,
+ "system-mode allocation on free rows failed: %s", strerror(error));
+ amd_test_release_pmc(sys);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, alloc_reserves_distinct_rows);
+ ATF_TP_ADD_TC(tp, failed_alloc_leaves_no_trace);
+ ATF_TP_ADD_TC(tp, thread_rows_refuse_system_pmc);
+
+ return (atf_no_error());
+}
Index: tests/sys/pmc/amd/pmc_start_msr_test.c
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmc_start_msr_test.c
@@ -0,0 +1,263 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ * Author: Davi C.A <davi.chavesazevedo@amd.com>
+ * Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+ */
+
+#include <sys/param.h>
+#include <sys/cpuctl.h>
+#include <sys/cpuset.h>
+#include <sys/ioctl.h>
+#include <sys/pmc.h>
+#include <sys/proc.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <dev/hwpmc/hwpmc_amd.h>
+
+#include <atf-c.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pmc.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "amd_pmc_test_common.h"
+
+#define CORE_EVENT "unhalted-cycles"
+/* PerfEvtSel of core counter n when PerfCtrExtCore is present. */
+#define PERF_CTL_MSR(n) (AMD_PMC_CORE_BASE + 2 * (n))
+
+static void
+skip_unless_ready(void)
+{
+ struct amd_test_cpu cpu;
+
+ amd_test_skip_unless_zen(&cpu);
+ amd_test_skip_unless_perfctr_extcore();
+ amd_test_skip_unless_hwpmc();
+ if (!pmc_pmu_enabled())
+ atf_tc_skip("pmu-events not enabled");
+}
+
+static uint64_t
+rdmsr_on(int cpu, uint32_t msr)
+{
+ cpuctl_msr_args_t args;
+ char path[32];
+ int fd;
+
+ (void)snprintf(path, sizeof(path), "/dev/cpuctl%d", cpu);
+ fd = open(path, O_RDONLY);
+ ATF_REQUIRE_MSG(fd >= 0, "open(%s) failed: %s", path, strerror(errno));
+ args.msr = msr;
+ args.data = 0;
+ ATF_REQUIRE_MSG(ioctl(fd, CPUCTL_RDMSR, &args) == 0,
+ "CPUCTL_RDMSR(%#x) on cpu%d failed: %s", msr, cpu,
+ strerror(errno));
+ close(fd);
+ return (args.data);
+}
+
+static unsigned int
+core_counter(pmc_id_t id)
+{
+ struct pmc_pmcinfo *pmcinfo;
+ unsigned int row;
+ int n;
+
+ row = PMC_ID_TO_ROWINDEX(id);
+ ATF_REQUIRE(pmc_pmcinfo(0, &pmcinfo) == 0);
+ n = amd_test_core_index(pmcinfo->pm_pmcs[row].pm_name);
+ ATF_REQUIRE_MSG(n >= 0, "row %u is %s, not a core counter", row,
+ pmcinfo->pm_pmcs[row].pm_name);
+ free(pmcinfo);
+ return ((unsigned int)n);
+}
+
+static void
+wait_until_sleeping(pid_t pid)
+{
+ struct kinfo_proc kp;
+ size_t len;
+ int i, mib[4];
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ for (i = 0; i < 1000; i++) {
+ len = sizeof(kp);
+ ATF_REQUIRE_MSG(sysctl(mib, nitems(mib), &kp, &len, NULL,
+ 0) == 0, "kern.proc.pid.%d: %s", pid, strerror(errno));
+ if (kp.ki_stat == SSLEEP)
+ return;
+ usleep(1000);
+ }
+ atf_tc_fail("process %d did not go to sleep", pid);
+}
+
+static bool
+evsel_enabled(int cpu, unsigned int counter)
+{
+ return ((rdmsr_on(cpu, PERF_CTL_MSR(counter)) & AMD_PMC_ENABLE) != 0);
+}
+
+ATF_TC(system_start_enables_now);
+ATF_TC_HEAD(system_start_enables_now, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmc_start() of a system-mode core PMC on cpu0 sets PerfEvtSel.En "
+ "on cpu0 right away, and pmc_stop() clears it");
+ atf_tc_set_md_var(tc, "require.kmods", "cpuctl");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(system_start_enables_now, tc)
+{
+ unsigned int ctr;
+ pmc_id_t id;
+
+ skip_unless_ready();
+ ATF_REQUIRE_MSG(pmc_allocate(CORE_EVENT, PMC_MODE_SC, 0, 0, &id,
+ 0) == 0, "pmc_allocate(%s, SC) failed: %s", CORE_EVENT,
+ strerror(errno));
+ ctr = core_counter(id);
+ ATF_CHECK_MSG(!evsel_enabled(0, ctr),
+ "counter %u was enabled before pmc_start()", ctr);
+
+ ATF_REQUIRE(pmc_start(id) == 0);
+ ATF_CHECK_MSG(evsel_enabled(0, ctr),
+ "pmc_start() did not set PerfEvtSel.En of counter %u", ctr);
+ ATF_REQUIRE(pmc_stop(id) == 0);
+ ATF_CHECK_MSG(!evsel_enabled(0, ctr),
+ "pmc_stop() did not clear PerfEvtSel.En of counter %u", ctr);
+ amd_test_release_pmc(id);
+}
+
+ATF_TC(system_start_sets_global_ctl);
+ATF_TC_HEAD(system_start_sets_global_ctl, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "With PerfMonV2, pmc_start() of a system-mode core PMC on cpu0 "
+ "sets its PerfCntrGlobalCtl bit on cpu0, and pmc_stop() clears it");
+ atf_tc_set_md_var(tc, "require.kmods", "cpuctl");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(system_start_sets_global_ctl, tc)
+{
+ unsigned int ctr;
+ pmc_id_t id;
+ size_t len;
+ bool v2;
+
+ skip_unless_ready();
+ len = sizeof(v2);
+ if (sysctlbyname("kern.hwpmc.amd_perfmon_v2", &v2, &len, NULL,
+ 0) != 0 || !v2)
+ atf_tc_skip("hwpmc does not use PerfMonV2 on this CPU");
+ ATF_REQUIRE_MSG(pmc_allocate(CORE_EVENT, PMC_MODE_SC, 0, 0, &id,
+ 0) == 0, "pmc_allocate(%s, SC) failed: %s", CORE_EVENT,
+ strerror(errno));
+ ctr = core_counter(id);
+ ATF_CHECK_MSG((rdmsr_on(0, AMD_PMC_GLOBAL_CTL) & (1ULL << ctr)) == 0,
+ "counter %u was enabled before pmc_start()", ctr);
+ ATF_REQUIRE(pmc_start(id) == 0);
+ ATF_CHECK_MSG((rdmsr_on(0, AMD_PMC_GLOBAL_CTL) & (1ULL << ctr)) != 0,
+ "pmc_start() did not set the GlobalCtl bit of counter %u", ctr);
+ ATF_REQUIRE(pmc_stop(id) == 0);
+ ATF_CHECK_MSG((rdmsr_on(0, AMD_PMC_GLOBAL_CTL) & (1ULL << ctr)) == 0,
+ "pmc_stop() did not clear the GlobalCtl bit of counter %u", ctr);
+ amd_test_release_pmc(id);
+}
+
+ATF_TC(process_start_waits_for_target);
+ATF_TC_HEAD(process_start_waits_for_target, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "pmc_start() of a process-mode core PMC attached to a child that "
+ "is bound to cpu1 and blocked in read(2) leaves PerfEvtSel.En "
+ "clear there; the PMC counts once the child runs");
+ atf_tc_set_md_var(tc, "require.kmods", "cpuctl");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(process_start_waits_for_target, tc)
+{
+ cpuset_t mask;
+ volatile uint64_t sink;
+ uint64_t i;
+ pid_t child;
+ pmc_id_t id;
+ pmc_value_t v;
+ unsigned int ctr;
+ int go[2], status;
+ char c;
+
+ skip_unless_ready();
+ if (pmc_ncpu() < 2)
+ atf_tc_skip("needs at least two CPUs");
+
+ ATF_REQUIRE(pipe(go) == 0);
+ child = fork();
+ ATF_REQUIRE(child >= 0);
+ if (child == 0) {
+ close(go[1]);
+ if (read(go[0], &c, 1) != 1)
+ _exit(1);
+ sink = 0;
+ for (i = 0; i < 50000000; i++)
+ sink += i;
+ _exit(0);
+ }
+ close(go[0]);
+
+ CPU_ZERO(&mask);
+ CPU_SET(1, &mask);
+ if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, child,
+ sizeof(mask), &mask) != 0) {
+ (void)kill(child, SIGKILL);
+ (void)waitpid(child, NULL, 0);
+ atf_tc_skip("cannot bind the child to cpu1: %s",
+ strerror(errno));
+ }
+ wait_until_sleeping(child);
+
+ ATF_REQUIRE_MSG(pmc_allocate(CORE_EVENT, PMC_MODE_TC, 0, PMC_CPU_ANY,
+ &id, 0) == 0, "pmc_allocate(%s, TC) failed: %s", CORE_EVENT,
+ strerror(errno));
+ ctr = core_counter(id);
+ ATF_REQUIRE(pmc_attach(id, child) == 0);
+ ATF_REQUIRE(pmc_start(id) == 0);
+
+ /* The child is blocked in read(2), so nothing has loaded the PMC. */
+ ATF_CHECK_MSG(!evsel_enabled(1, ctr),
+ "counter %u was enabled on cpu1 before the target ran", ctr);
+
+ ATF_REQUIRE(write(go[1], "g", 1) == 1);
+ close(go[1]);
+ ATF_REQUIRE(waitpid(child, &status, 0) == child);
+ ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "child failed: status %#x", status);
+
+ /* hwpmc saved the child's count when it exited. */
+ ATF_REQUIRE(pmc_read(id, &v) == 0);
+ ATF_CHECK_MSG(v > 0, "the PMC did not count while the target ran");
+ amd_test_release_pmc(id);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, system_start_enables_now);
+ ATF_TP_ADD_TC(tp, system_start_sets_global_ctl);
+ ATF_TP_ADD_TC(tp, process_start_waits_for_target);
+
+ return (atf_no_error());
+}
Index: tests/sys/pmc/amd/pmcstat_ibs_test.sh
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmcstat_ibs_test.sh
@@ -0,0 +1,63 @@
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Advanced Micro Devices, Inc.
+# Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+#
+
+pmcstat_ibs_setup()
+{
+ case "$(sysctl -n kern.hwpmc.cpuid 2>/dev/null)" in
+ AuthenticAMD-*) ;;
+ *) atf_skip "hwpmc(4) on an AMD CPU is required" ;;
+ esac
+ pmccontrol -l 2>/dev/null |
+ awk '$3 == "IBS" { found = 1 } END { exit !found }' ||
+ atf_skip "hwpmc does not expose the IBS class"
+}
+
+# System sampling: a short process-mode target may never be switched in.
+pmcstat_ibs_record_replay()
+{
+ pmcstat -O ibs.log "$@" -- dd if=/dev/zero of=/dev/null bs=4k \
+ count=500000 > /dev/null 2> record.err ||
+ atf_fail "pmcstat failed: $(cat record.err)"
+ pmcstat -R ibs.log -o ibs.txt > /dev/null 2> replay.err ||
+ atf_fail "pmcstat -R failed: $(cat replay.err)"
+}
+
+atf_test_case fetch_record_replay
+fetch_record_replay_head()
+{
+ atf_set "descr" "pmcstat records IBS-FETCH samples and replays the log"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "pmcstat pmccontrol"
+}
+fetch_record_replay_body()
+{
+ pmcstat_ibs_setup
+ pmcstat_ibs_record_replay -S ibs-fetch
+ grep -q "^ibs-fetch" ibs.txt || atf_fail "no IBS-FETCH sample replayed"
+}
+
+atf_test_case fetch_and_op_record_replay
+fetch_and_op_record_replay_head()
+{
+ atf_set "descr" \
+ "pmcstat records IBS-FETCH and IBS-OP samples and replays both"
+ atf_set "require.user" "root"
+ atf_set "require.progs" "pmcstat pmccontrol"
+}
+fetch_and_op_record_replay_body()
+{
+ pmcstat_ibs_setup
+ pmcstat_ibs_record_replay -S ibs-fetch -S ibs-op
+ grep -q "^ibs-fetch" ibs.txt || atf_fail "no IBS-FETCH sample replayed"
+ grep -q "^ibs-op" ibs.txt || atf_fail "no IBS-OP sample replayed"
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case fetch_record_replay
+ atf_add_test_case fetch_and_op_record_replay
+}
Index: tests/sys/pmc/amd/pmu_perfmonv2_test.c
===================================================================
--- /dev/null
+++ tests/sys/pmc/amd/pmu_perfmonv2_test.c
@@ -0,0 +1,185 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ * Author: Osvaldo Janeri Filho <ojanerif@amd.com>
+ */
+
+#include <sys/param.h>
+#include <sys/pmc.h>
+#include <sys/sysctl.h>
+
+#include <dev/hwpmc/hwpmc_amd.h>
+
+#include <atf-c.h>
+
+#include <errno.h>
+#include <pmc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "amd_pmc_test_common.h"
+
+#define LOOP_ITERS 5000000UL
+
+static bool
+perfmon_v2_sysctl(bool *v2)
+{
+ size_t len;
+
+ len = sizeof(*v2);
+ return (sysctlbyname("kern.hwpmc.amd_perfmon_v2", v2, &len, NULL,
+ 0) == 0);
+}
+
+static void
+skip_unless_v2(void)
+{
+ struct amd_test_cpu cpu;
+ bool v2;
+
+ amd_test_skip_unless_zen(&cpu);
+ amd_test_skip_unless_hwpmc();
+ ATF_REQUIRE_MSG(perfmon_v2_sysctl(&v2),
+ "kern.hwpmc.amd_perfmon_v2 is missing: %s", strerror(errno));
+ if (!v2)
+ atf_tc_skip("hwpmc does not use PerfMonV2 on this CPU");
+ if (!pmc_pmu_enabled())
+ atf_tc_skip("pmu-events not enabled");
+}
+
+static void __noinline
+loop(uint64_t iters)
+{
+ volatile uint64_t sink;
+ uint64_t i;
+
+ sink = 0;
+ for (i = 0; i < iters; i++)
+ sink += i * 3 + 7;
+}
+
+ATF_TC(pmv2_sysctl_matches_cpuid);
+ATF_TC_HEAD(pmv2_sysctl_matches_cpuid, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "kern.hwpmc.amd_perfmon_v2 is set exactly when CPUID "
+ "Fn8000_0022 EAX[0] is set on family 19h or later");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(pmv2_sysctl_matches_cpuid, tc)
+{
+ struct amd_test_cpu cpu;
+ u_int regs[4];
+ bool expected, v2;
+
+ amd_test_skip_unless_zen(&cpu);
+ amd_test_skip_unless_hwpmc();
+ ATF_REQUIRE_MSG(perfmon_v2_sysctl(&v2),
+ "kern.hwpmc.amd_perfmon_v2 is missing: %s", strerror(errno));
+
+ do_cpuid(0x80000000, regs);
+ expected = false;
+ if (regs[0] >= CPUID_EXTPERFMON) {
+ do_cpuid(CPUID_EXTPERFMON, regs);
+ expected = EXTPERFMON_PERFMONV2(regs[0]) != 0 &&
+ cpu.family >= 0x19;
+ }
+ ATF_CHECK_MSG(v2 == expected,
+ "kern.hwpmc.amd_perfmon_v2 is %d, CPUID says %d", v2, expected);
+}
+
+ATF_TC(pmv2_process_counting);
+ATF_TC_HEAD(pmv2_process_counting, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "With PerfMonV2, two process-mode PMCs count a known loop: at "
+ "least one retired instruction per iteration, and a plausible IPC");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(pmv2_process_counting, tc)
+{
+ pmc_id_t cyc, instr;
+ pmc_value_t vcyc, vinstr;
+
+ skip_unless_v2();
+ ATF_REQUIRE_MSG(pmc_allocate("ex_ret_instr", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &instr, 0) == 0, "ex_ret_instr allocation failed: %s",
+ strerror(errno));
+ ATF_REQUIRE_MSG(pmc_allocate("ls_not_halted_cyc", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &cyc, 0) == 0,
+ "ls_not_halted_cyc allocation failed: %s", strerror(errno));
+ ATF_REQUIRE(pmc_attach(instr, 0) == 0);
+ ATF_REQUIRE(pmc_attach(cyc, 0) == 0);
+ ATF_REQUIRE(pmc_start(instr) == 0);
+ ATF_REQUIRE(pmc_start(cyc) == 0);
+ loop(LOOP_ITERS);
+ /* A stopped PMC reads its last saved value. */
+ ATF_REQUIRE(pmc_read(instr, &vinstr) == 0);
+ ATF_REQUIRE(pmc_read(cyc, &vcyc) == 0);
+ ATF_REQUIRE(pmc_stop(instr) == 0);
+ ATF_REQUIRE(pmc_stop(cyc) == 0);
+ amd_test_release_pmc(instr);
+ amd_test_release_pmc(cyc);
+
+ ATF_CHECK_MSG(vinstr >= LOOP_ITERS,
+ "ex_ret_instr counted %ju over a %lu-iteration loop",
+ (uintmax_t)vinstr, LOOP_ITERS);
+ ATF_CHECK_MSG(vcyc > 0 && vinstr / vcyc < 20,
+ "implausible counts: %ju instructions in %ju cycles",
+ (uintmax_t)vinstr, (uintmax_t)vcyc);
+}
+
+ATF_TC(pmv2_all_core_counters);
+ATF_TC_HEAD(pmv2_all_core_counters, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "With PerfMonV2, every core counter held by one process counts "
+ "the same loop, so the global enable reaches all of them");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(pmv2_all_core_counters, tc)
+{
+ pmc_id_t ids[AMD_PMC_CORE_MAX];
+ pmc_value_t v;
+ int i, n;
+
+ skip_unless_v2();
+ amd_test_skip_unless_perfctr_extcore();
+ n = amd_test_count_core_rows(0, false);
+ ATF_REQUIRE_MSG(n > 0 && n <= AMD_PMC_CORE_MAX,
+ "unexpected core row count %d", n);
+ if (amd_test_count_core_rows(0, true) < n)
+ atf_tc_skip("not all core counters are free");
+
+ for (i = 0; i < n; i++) {
+ ATF_REQUIRE_MSG(pmc_allocate("ex_ret_instr", PMC_MODE_TC, 0,
+ PMC_CPU_ANY, &ids[i], 0) == 0,
+ "allocation %d of %d failed: %s", i, n, strerror(errno));
+ ATF_REQUIRE(pmc_attach(ids[i], 0) == 0);
+ }
+ for (i = 0; i < n; i++)
+ ATF_REQUIRE(pmc_start(ids[i]) == 0);
+ loop(LOOP_ITERS);
+ /* A stopped PMC reads its last saved value. */
+ for (i = 0; i < n; i++) {
+ ATF_REQUIRE(pmc_read(ids[i], &v) == 0);
+ ATF_CHECK_MSG(v >= LOOP_ITERS,
+ "counter %d of %d counted %ju over a %lu-iteration loop",
+ i, n, (uintmax_t)v, LOOP_ITERS);
+ }
+ for (i = 0; i < n; i++) {
+ ATF_REQUIRE(pmc_stop(ids[i]) == 0);
+ amd_test_release_pmc(ids[i]);
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, pmv2_sysctl_matches_cpuid);
+ ATF_TP_ADD_TC(tp, pmv2_process_counting);
+ ATF_TP_ADD_TC(tp, pmv2_all_core_counters);
+
+ return (atf_no_error());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 8, 7:22 PM (5 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41574893
Default Alt Text
D61063.1794165725.diff (48 KB)
Attached To
Mode
D61063: tests/sys/pmc/amd: add IBS, row allocation, PerfEvtSel and PerfMonV2 tests
Attached
Detach File
Event Timeline