git: e555692d1bbb - main - hwpmc: Add ATF regression tests for hwpmc EXTERROR diagnostics

From: Mitchell Horne <mhorne_at_FreeBSD.org>
Date: Thu, 03 Sep 2026 17:17:33 UTC
The branch main has been updated by mhorne:

URL: https://cgit.FreeBSD.org/src/commit/?id=e555692d1bbbe8918054746f21013fff6256974e

commit e555692d1bbbe8918054746f21013fff6256974e
Author:     Andre Silva <andasilv@amd.com>
AuthorDate: 2026-06-12 15:39:16 +0000
Commit:     Mitchell Horne <mhorne@FreeBSD.org>
CommitDate: 2026-09-03 17:17:24 +0000

    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
---
 tests/sys/pmc/Makefile          |   5 +
 tests/sys/pmc/pmc_exterr_test.c | 449 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 454 insertions(+)

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());
+}