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
  6c4d9b9af1
- 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 <[email protected]>
Signed-off-by:	Andre Silva <[email protected]>
Co-authored-by:	mhorne
Sponsored by:	AMD
Pull Request:	https://github.com/freebsd/freebsd-src/pull/2180
This commit is contained in:
Andre Silva authored and Mitchell Horne committed 2026-09-03 14:17:24 -03:00
1 parent f19f44634a
commit e555692d1b
2 files changed
+454

No files matched your search

+5
View File
@@ -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>
+449
View File
@@ -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());
}