Page MenuHomeFreeBSD

D61063.1794165725.diff
No OneTemporary

Authored By
Unknown
Size
48 KB
Referenced Files
None
Subscribers
None

D61063.1794165725.diff

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

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)

Event Timeline