aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndre Silva <andasilv@amd.com>2026-06-12 15:39:16 +0000
committerMitchell Horne <mhorne@FreeBSD.org>2026-09-03 17:17:24 +0000
commite555692d1bbbe8918054746f21013fff6256974e (patch)
treeda6d1f3313b5efbe8f5b38d7edc324cd56b6d60e
parentf19f44634afd859e445c7b13bbdf715a493b499b (diff)
hwpmc: Add ATF regression tests for hwpmc EXTERROR diagnostics
Root-only ATF program hitting negative allocate/attach/read-write paths and asserting the exterr(3) text. AMD/IBS cases skip without the PMC class; program skips without hwpmc. Additional changes by mhorne@: - Move and rename to the established test directory tests/sys/pmc - Remove broken test amd_missing_pmu_flag; fixed by recent change 6c4d9b9af1a3 - Add ATF_REQUIRE_FEATURE("exterr_strings") to skip the tests on kernels compiled without the strings - Remove arch-conditional compilation; tests are properly gated by PMC class check - Fix copyright formatting Reviewed by: Ali Mashtizadeh <ali@mashtizadeh.com> Signed-off-by: Andre Silva <andasilv@amd.com> Co-authored-by: mhorne Sponsored by: AMD Pull Request: https://github.com/freebsd/freebsd-src/pull/2180
-rw-r--r--tests/sys/pmc/Makefile5
-rw-r--r--tests/sys/pmc/pmc_exterr_test.c449
2 files changed, 454 insertions, 0 deletions
diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
index c9c138250932..311b2ef6343c 100644
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -4,11 +4,16 @@ ATF_TESTS_C+= pmc_api_test
ATF_TESTS_C+= pmc_credexec_test
ATF_TESTS_C+= pmc_detach_test
ATF_TESTS_C+= pmc_exec_test
+ATF_TESTS_C+= pmc_exterr_test
ATF_TESTS_C+= pmc_lifecycle_test
ATF_TESTS_C+= pmc_log_test
ATF_TESTS_C+= pmc_wrap_test
+TEST_METADATA.pmc_exterr_test+= required_user="root"
+
LIBADD+= pmc
LIBADD.pmc_detach_test+= pthread
+CFLAGS.pmc_exterr_test.c+= -I${SRCTOP}/tests
+
.include <bsd.test.mk>
diff --git a/tests/sys/pmc/pmc_exterr_test.c b/tests/sys/pmc/pmc_exterr_test.c
new file mode 100644
index 000000000000..bc4d260f2ed4
--- /dev/null
+++ b/tests/sys/pmc/pmc_exterr_test.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <sys/exterrvar.h>
+#include <sys/module.h>
+#include <sys/pmc.h>
+
+#include <dev/hwpmc/hwpmc_amd.h>
+#include <dev/hwpmc/hwpmc_ibs.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <exterr.h>
+#include <pmc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+
+static int
+hwpmc_syscall_num(void)
+{
+ struct module_stat ms;
+ int modid;
+
+ modid = modfind(PMC_MODULE_NAME);
+ if (modid < 0)
+ return (-1);
+
+ ms.version = sizeof(ms);
+ if (modstat(modid, &ms) < 0)
+ return (-1);
+
+ return (ms.data.intval);
+}
+
+static int
+hwpmc_call(int op, void *arg)
+{
+ int sc;
+
+ sc = hwpmc_syscall_num();
+ if (sc < 0)
+ return (-1);
+
+ return (syscall(sc, op, arg));
+}
+
+static void
+clear_exterr(void)
+{
+ char exterr[UEXTERROR_MAXLEN];
+
+ ATF_REQUIRE_ERRNO(EINVAL, exterrctl(EXTERRCTL_UD, 0, NULL) == -1);
+ ATF_REQUIRE_EQ(0, uexterr_gettext(exterr, sizeof(exterr)));
+ ATF_REQUIRE_STREQ("", exterr);
+}
+
+static void
+require_exterr(const char *needle)
+{
+ char exterr[UEXTERROR_MAXLEN];
+
+ ATF_REQUIRE_EQ(0, uexterr_gettext(exterr, sizeof(exterr)));
+ ATF_REQUIRE_MSG(strstr(exterr, needle) != NULL,
+ "extended error \"%s\" does not contain \"%s\"", exterr, needle);
+}
+
+static void
+require_hwpmc(void)
+{
+
+ if (pmc_init() == 0)
+ return;
+
+ atf_tc_skip("hwpmc is unavailable: %s", strerror(errno));
+}
+
+static void
+require_class(enum pmc_class class, const char *name)
+{
+ const struct pmc_cpuinfo *pci;
+ uint32_t i;
+
+ ATF_REQUIRE_EQ(0, pmc_cpuinfo(&pci));
+ for (i = 0; i < pci->pm_nclass; i++) {
+ if (pci->pm_classes[i].pm_class == class)
+ return;
+ }
+ atf_tc_skip("%s PMCs are unavailable on this system", name);
+}
+
+static void
+allocate_soft_pmc(enum pmc_mode mode, int cpu, pmc_id_t *pmcid)
+{
+
+ ATF_REQUIRE_EQ(0, pmc_allocate("SOFT-CLOCK.HARD", mode, 0, cpu, pmcid,
+ 1));
+}
+
+ATF_TC_WITHOUT_HEAD(pmcallocate_invalid_mode);
+ATF_TC_BODY(pmcallocate_invalid_mode, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_SOFT;
+ pa.pm_mode = (enum pmc_mode)0xff;
+ pa.pm_cpu = PMC_CPU_ANY;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("Invalid PMC mode");
+}
+
+ATF_TC_WITHOUT_HEAD(pmcallocate_invalid_cpu);
+ATF_TC_BODY(pmcallocate_invalid_cpu, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_SOFT;
+ pa.pm_mode = PMC_MODE_TC;
+ pa.pm_cpu = (uint32_t)pmc_ncpu();
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("Invalid PMC CPU");
+}
+
+ATF_TC_WITHOUT_HEAD(pmcallocate_invalid_flags);
+ATF_TC_BODY(pmcallocate_invalid_flags, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_SOFT;
+ pa.pm_mode = PMC_MODE_TC;
+ pa.pm_cpu = PMC_CPU_ANY;
+ pa.pm_flags = 1U << 31;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("Invalid PMC flags");
+}
+
+ATF_TC_WITHOUT_HEAD(pmcattach_system_mode);
+ATF_TC_BODY(pmcattach_system_mode, tc)
+{
+ struct pmc_op_pmcattach pa;
+ pmc_id_t pmcid;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+ allocate_soft_pmc(PMC_MODE_SC, 0, &pmcid);
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_pmc = pmcid;
+ pa.pm_pid = getpid();
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCATTACH, &pa) == -1);
+ require_exterr("Cannot attach a system-mode PMC");
+
+ ATF_REQUIRE_EQ(0, pmc_release(pmcid));
+}
+
+ATF_TC_WITHOUT_HEAD(pmcattach_running_pmc);
+ATF_TC_BODY(pmcattach_running_pmc, tc)
+{
+ struct pmc_op_pmcattach pa;
+ pmc_id_t pmcid;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+ allocate_soft_pmc(PMC_MODE_TC, PMC_CPU_ANY, &pmcid);
+ ATF_REQUIRE_EQ(0, pmc_start(pmcid));
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_pmc = pmcid;
+ pa.pm_pid = getpid();
+
+ ATF_REQUIRE_ERRNO(EBUSY, hwpmc_call(PMC_OP_PMCATTACH, &pa) == -1);
+ require_exterr("PMC must be stopped before attach");
+
+ ATF_REQUIRE_EQ(0, pmc_stop(pmcid));
+ ATF_REQUIRE_EQ(0, pmc_release(pmcid));
+}
+
+ATF_TC_WITHOUT_HEAD(pmcrw_no_flags);
+ATF_TC_BODY(pmcrw_no_flags, tc)
+{
+ struct pmc_op_pmcrw prw;
+ pmc_id_t pmcid;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+ allocate_soft_pmc(PMC_MODE_TC, PMC_CPU_ANY, &pmcid);
+
+ memset(&prw, 0, sizeof(prw));
+ prw.pm_pmcid = pmcid;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCRW, &prw) == -1);
+ require_exterr("PMCRW requires PMC_F_OLDVALUE");
+
+ ATF_REQUIRE_EQ(0, pmc_release(pmcid));
+}
+
+ATF_TC_WITHOUT_HEAD(pmcrw_write_running);
+ATF_TC_BODY(pmcrw_write_running, tc)
+{
+ struct pmc_op_pmcrw prw;
+ pmc_id_t pmcid;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ clear_exterr();
+ allocate_soft_pmc(PMC_MODE_TC, PMC_CPU_ANY, &pmcid);
+ ATF_REQUIRE_EQ(0, pmc_start(pmcid));
+
+ memset(&prw, 0, sizeof(prw));
+ prw.pm_flags = PMC_F_NEWVALUE;
+ prw.pm_pmcid = pmcid;
+ prw.pm_value = 1;
+
+ ATF_REQUIRE_ERRNO(EBUSY, hwpmc_call(PMC_OP_PMCRW, &prw) == -1);
+ require_exterr("Cannot write a PMC while it is running");
+
+ ATF_REQUIRE_EQ(0, pmc_stop(pmcid));
+ ATF_REQUIRE_EQ(0, pmc_release(pmcid));
+}
+
+ATF_TC_WITHOUT_HEAD(amd_invalid_subclass);
+ATF_TC_BODY(amd_invalid_subclass, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_K8, "AMD");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_K8;
+ pa.pm_mode = PMC_MODE_TC;
+ pa.pm_cpu = PMC_CPU_ANY;
+ pa.pm_flags = PMC_F_EV_PMU;
+ pa.pm_md.pm_amd.pm_amd_sub_class = UINT32_MAX;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("No PMC row accepted");
+}
+
+ATF_TC_WITHOUT_HEAD(amd_invalid_config_bits);
+ATF_TC_BODY(amd_invalid_config_bits, tc)
+{
+ struct pmc_op_pmcallocate pa;
+ char exterr[UEXTERROR_MAXLEN];
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_K8, "AMD");
+ if (!pmc_pmu_enabled())
+ atf_tc_skip("AMD PMU raw allocation path is unavailable");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_K8;
+ pa.pm_mode = PMC_MODE_TC;
+ pa.pm_cpu = PMC_CPU_ANY;
+ pa.pm_flags = PMC_F_EV_PMU;
+ pa.pm_md.pm_amd.pm_amd_sub_class = PMC_AMD_SUB_CLASS_CORE;
+ pa.pm_md.pm_amd.pm_amd_config = ~0ULL;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ ATF_REQUIRE_EQ(0, uexterr_gettext(exterr, sizeof(exterr)));
+ if (strstr(exterr, "No PMC row accepted") != NULL)
+ atf_tc_skip("AMD core reserved-bit diagnostic was not returned");
+ ATF_REQUIRE_MSG(strstr(exterr,
+ "AMD PMU config has unsupported bits") != NULL,
+ "extended error \"%s\" does not report core reserved-bit failure",
+ exterr);
+}
+
+/*
+ * DF subclass with reserved config bits. On a system with Data Fabric
+ * PMC support, the DF row matches class+subclass and the config-mask
+ * check fires with the "AMD PMU config has unsupported bits" diagnostic.
+ * On a system without DF support every row mismatches subclass and we
+ * surface "No PMC row accepted"; the test skips in that case rather
+ * than fail.
+ */
+ATF_TC_WITHOUT_HEAD(amd_invalid_df_config_bits);
+ATF_TC_BODY(amd_invalid_df_config_bits, tc)
+{
+ struct pmc_op_pmcallocate pa;
+ char exterr[UEXTERROR_MAXLEN];
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_K8, "AMD");
+ if (!pmc_pmu_enabled())
+ atf_tc_skip("AMD PMU raw allocation path is unavailable");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_K8;
+ pa.pm_mode = PMC_MODE_SC;
+ pa.pm_cpu = 0;
+ pa.pm_flags = PMC_F_EV_PMU;
+ pa.pm_md.pm_amd.pm_amd_sub_class = PMC_AMD_SUB_CLASS_DATA_FABRIC;
+ pa.pm_md.pm_amd.pm_amd_config = ~0ULL;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ ATF_REQUIRE_EQ(0, uexterr_gettext(exterr, sizeof(exterr)));
+ if (strstr(exterr, "No PMC row accepted") != NULL)
+ atf_tc_skip("Data Fabric PMCs are unavailable on this system");
+ ATF_REQUIRE_MSG(strstr(exterr,
+ "AMD PMU config has unsupported bits") != NULL,
+ "extended error \"%s\" does not report DF reserved-bit failure",
+ exterr);
+ fprintf(stderr, "DF reserved-bit allocation: errno=EINVAL exterr=%s\n",
+ exterr);
+}
+
+ATF_TC_WITHOUT_HEAD(ibs_missing_system_capability);
+ATF_TC_BODY(ibs_missing_system_capability, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_IBS, "IBS");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_class = PMC_CLASS_IBS;
+ pa.pm_mode = PMC_MODE_SS;
+ pa.pm_cpu = 0;
+ pa.pm_md.pm_ibs.ibs_type = IBS_PMC_OP;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("IBS requires SYSTEM capability");
+}
+
+/*
+ * An out-of-range ibs_type is a row discriminator mismatch on every IBS
+ * row (bare EINVAL), so the generic loop-exhaustion message is surfaced.
+ */
+ATF_TC_WITHOUT_HEAD(ibs_invalid_type);
+ATF_TC_BODY(ibs_invalid_type, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_IBS, "IBS");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_caps = PMC_CAP_SYSTEM;
+ pa.pm_class = PMC_CLASS_IBS;
+ pa.pm_mode = PMC_MODE_SS;
+ pa.pm_cpu = 0;
+ pa.pm_md.pm_ibs.ibs_type = UINT32_MAX;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("No PMC row accepted");
+}
+
+ATF_TC_WITHOUT_HEAD(ibs_invalid_config_bits);
+ATF_TC_BODY(ibs_invalid_config_bits, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_IBS, "IBS");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_caps = PMC_CAP_SYSTEM;
+ pa.pm_class = PMC_CLASS_IBS;
+ pa.pm_mode = PMC_MODE_SS;
+ pa.pm_cpu = 0;
+ pa.pm_md.pm_ibs.ibs_type = IBS_PMC_OP;
+ pa.pm_md.pm_ibs.ibs_ctl = 1ULL << 58;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("IBS op ctl config");
+}
+
+ATF_TC_WITHOUT_HEAD(ibs_nonsampling_mode);
+ATF_TC_BODY(ibs_nonsampling_mode, tc)
+{
+ struct pmc_op_pmcallocate pa;
+
+ ATF_REQUIRE_FEATURE("exterr_strings");
+ require_hwpmc();
+ require_class(PMC_CLASS_IBS, "IBS");
+ clear_exterr();
+
+ memset(&pa, 0, sizeof(pa));
+ pa.pm_caps = PMC_CAP_SYSTEM;
+ pa.pm_class = PMC_CLASS_IBS;
+ pa.pm_mode = PMC_MODE_SC;
+ pa.pm_cpu = 0;
+ pa.pm_md.pm_ibs.ibs_type = IBS_PMC_OP;
+
+ ATF_REQUIRE_ERRNO(EINVAL, hwpmc_call(PMC_OP_PMCALLOCATE, &pa) == -1);
+ require_exterr("IBS only supports sampling mode");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, pmcallocate_invalid_mode);
+ ATF_TP_ADD_TC(tp, pmcallocate_invalid_cpu);
+ ATF_TP_ADD_TC(tp, pmcallocate_invalid_flags);
+ ATF_TP_ADD_TC(tp, pmcattach_system_mode);
+ ATF_TP_ADD_TC(tp, pmcattach_running_pmc);
+ ATF_TP_ADD_TC(tp, pmcrw_no_flags);
+ ATF_TP_ADD_TC(tp, pmcrw_write_running);
+ ATF_TP_ADD_TC(tp, amd_invalid_subclass);
+ ATF_TP_ADD_TC(tp, amd_invalid_config_bits);
+ ATF_TP_ADD_TC(tp, amd_invalid_df_config_bits);
+ ATF_TP_ADD_TC(tp, ibs_missing_system_capability);
+ ATF_TP_ADD_TC(tp, ibs_invalid_type);
+ ATF_TP_ADD_TC(tp, ibs_invalid_config_bits);
+ ATF_TP_ADD_TC(tp, ibs_nonsampling_mode);
+
+ return (atf_no_error());
+}