Page MenuHomeFreeBSD

D60766.1794711119.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D60766.1794711119.diff

Index: etc/mtree/BSD.tests.dist
===================================================================
--- etc/mtree/BSD.tests.dist
+++ etc/mtree/BSD.tests.dist
@@ -819,6 +819,8 @@
..
compat32
..
+ cpuctl
+ ..
devrandom
..
dtrace
Index: tests/sys/Makefile
===================================================================
--- tests/sys/Makefile
+++ tests/sys/Makefile
@@ -12,6 +12,7 @@
TESTS_SUBDIRS+= ${_cddl}
# XXX: Currently broken in CI
#TESTS_SUBDIRS+= compat32
+TESTS_SUBDIRS+= ${_cpuctl}
TESTS_SUBDIRS+= devrandom
TESTS_SUBDIRS+= fifo
TESTS_SUBDIRS+= file
@@ -47,6 +48,10 @@
_cddl= cddl
.endif
+.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "i386"
+_cpuctl= cpuctl
+.endif
+
.if ${MK_NETGRAPH} != "no"
_netgraph= netgraph
.endif
Index: tests/sys/cpuctl/Makefile
===================================================================
--- /dev/null
+++ tests/sys/cpuctl/Makefile
@@ -0,0 +1,9 @@
+TESTSDIR= ${TESTSBASE}/sys/cpuctl
+
+ATF_TESTS_C+= cpuctl_ibs_stray_nmi_test
+
+TEST_METADATA.cpuctl_ibs_stray_nmi_test+= is_exclusive="true"
+TEST_METADATA.cpuctl_ibs_stray_nmi_test+= required_kmods="cpuctl hwpmc"
+TEST_METADATA.cpuctl_ibs_stray_nmi_test+= required_user="root"
+
+.include <bsd.test.mk>
Index: tests/sys/cpuctl/cpuctl_ibs_stray_nmi_test.c
===================================================================
--- /dev/null
+++ tests/sys/cpuctl/cpuctl_ibs_stray_nmi_test.c
@@ -0,0 +1,310 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+/*
+ * Every IBS NMI hwpmc declines must be claimed after the cpuctl(4) write.
+ * machdep.panic_on_nmi is cleared so that an unfixed kernel fails instead
+ * of panicking.
+ */
+
+#include <sys/param.h>
+#include <sys/cpuctl.h>
+#include <sys/cpuset.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+
+#include <machine/specialreg.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define FETCH_EN (1ULL << 48)
+#define OP_EN (1ULL << 17)
+
+#define TOGGLES 20000
+#define FETCH_MAXCNT 0x100ULL /* complete within the spin window */
+#define OP_MAXCNT 0x1000ULL
+#define MAX_SPIN 200000 /* TSC ticks */
+
+#define STATE_FILE "ibs_stray_state"
+
+struct stray_state {
+ int panic_on_nmi;
+ uint64_t fetch;
+ uint64_t op;
+};
+
+static int
+msr_rw(int fd, u_long cmd, uint32_t msr, uint64_t *val)
+{
+ cpuctl_msr_args_t args;
+
+ args.msr = msr;
+ args.data = *val;
+ if (ioctl(fd, cmd, &args) != 0)
+ return (errno);
+ if (cmd == CPUCTL_RDMSR)
+ *val = args.data;
+ return (0);
+}
+
+static uint64_t
+intr_ignored(void)
+{
+ uint64_t v;
+ size_t len;
+
+ len = sizeof(v);
+ ATF_REQUIRE_MSG(sysctlbyname("kern.hwpmc.stats.intr_ignored", &v, &len,
+ NULL, 0) == 0, "kern.hwpmc.stats.intr_ignored: %s",
+ strerror(errno));
+ return (v);
+}
+
+/* A kernel without the fix has no counter and claims nothing. */
+static uint64_t
+nmis_claimed(void)
+{
+ u_long v;
+ size_t len;
+
+ len = sizeof(v);
+ if (sysctlbyname("machdep.ibs_ctl_write_nmis", &v, &len, NULL,
+ 0) != 0) {
+ ATF_REQUIRE_MSG(errno == ENOENT,
+ "machdep.ibs_ctl_write_nmis: %s", strerror(errno));
+ return (0);
+ }
+ return (v);
+}
+
+static void
+set_panic_on_nmi(int v)
+{
+ ATF_REQUIRE_MSG(sysctlbyname("machdep.panic_on_nmi", NULL, NULL, &v,
+ sizeof(v)) == 0, "machdep.panic_on_nmi: %s", strerror(errno));
+}
+
+static int
+setup(void)
+{
+ cpuctl_cpuid_args_t ext, vendor;
+ struct stray_state st;
+ uint64_t v;
+ size_t len;
+ FILE *f;
+ int error, fd;
+
+ fd = open("/dev/cpuctl0", O_RDWR);
+ if (fd < 0)
+ atf_tc_skip("/dev/cpuctl0: %s", strerror(errno));
+ vendor.level = 0;
+ ext.level = 0x80000001;
+ error = 0;
+ if (ioctl(fd, CPUCTL_CPUID, &vendor) != 0 ||
+ ioctl(fd, CPUCTL_CPUID, &ext) != 0)
+ error = errno;
+ if (error != 0) {
+ close(fd);
+ atf_tc_fail("CPUCTL_CPUID: %s", strerror(error));
+ }
+ if (!(vendor.data[1] == 0x68747541 && vendor.data[3] == 0x69746e65 &&
+ vendor.data[2] == 0x444d4163) && /* AuthenticAMD */
+ !(vendor.data[1] == 0x6f677948 && vendor.data[3] == 0x6e65476e &&
+ vendor.data[2] == 0x656e6975)) { /* HygonGenuine */
+ close(fd);
+ atf_tc_skip("not an AMD or Hygon CPU");
+ }
+ if ((ext.data[2] & AMDID2_IBS) == 0) {
+ close(fd);
+ atf_tc_skip("CPU does not implement IBS");
+ }
+ len = sizeof(v);
+ if (sysctlbyname("kern.hwpmc.stats.intr_ignored", &v, &len,
+ NULL, 0) != 0) {
+ close(fd);
+ atf_tc_skip("hwpmc is not loaded (IBS NMIs stay masked)");
+ }
+
+ len = sizeof(st.panic_on_nmi);
+ if (sysctlbyname("machdep.panic_on_nmi", &st.panic_on_nmi, &len,
+ NULL, 0) != 0)
+ error = errno;
+ st.fetch = st.op = 0;
+ if (error == 0)
+ error = msr_rw(fd, CPUCTL_RDMSR, MSR_AMD_IBS_FETCH_CTL,
+ &st.fetch);
+ if (error == 0)
+ error = msr_rw(fd, CPUCTL_RDMSR, MSR_AMD_IBS_OP_CTL, &st.op);
+ if (error != 0) {
+ close(fd);
+ atf_tc_fail("reading the initial state: %s", strerror(error));
+ }
+ if ((st.fetch & FETCH_EN) != 0 || (st.op & OP_EN) != 0) {
+ close(fd);
+ atf_tc_skip("IBS sampling is already enabled on CPU 0 "
+ "(fetch 0x%jx, op 0x%jx); run in a reserved environment "
+ "with no other IBS users", (uintmax_t)st.fetch,
+ (uintmax_t)st.op);
+ }
+
+ f = fopen(STATE_FILE, "w");
+ if (f == NULL || fwrite(&st, sizeof(st), 1, f) != 1 ||
+ fclose(f) != 0) {
+ close(fd);
+ atf_tc_fail("cannot save the state to %s", STATE_FILE);
+ }
+ return (fd);
+}
+
+static void
+toggle(const char *unit, uint32_t msr, uint64_t enable)
+{
+ cpuset_t set;
+ uint64_t claimed, deadline, ignored, val;
+ int fd, i;
+
+ CPU_ZERO(&set);
+ CPU_SET(0, &set);
+ ATF_REQUIRE(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1,
+ sizeof(set), &set) == 0);
+ fd = setup();
+ set_panic_on_nmi(0);
+
+ srandom((unsigned)__builtin_ia32_rdtsc());
+ ignored = intr_ignored();
+ claimed = nmis_claimed();
+ for (i = 0; i < TOGGLES; i++) {
+ val = enable;
+ ATF_REQUIRE_EQ(msr_rw(fd, CPUCTL_WRMSR, msr, &val), 0);
+ /* Let the disabling write land anywhere in the period. */
+ deadline = __builtin_ia32_rdtsc() +
+ (uint64_t)(random() % (MAX_SPIN + 1));
+ while (__builtin_ia32_rdtsc() < deadline)
+ ;
+ val = 0;
+ ATF_REQUIRE_EQ(msr_rw(fd, CPUCTL_WRMSR, msr, &val), 0);
+ }
+ close(fd);
+
+ ignored = intr_ignored() - ignored;
+ claimed = nmis_claimed() - claimed;
+ ATF_CHECK_MSG(ignored == claimed,
+ "%ju IBS %s NMIs declined by hwpmc, %ju claimed after the cpuctl "
+ "write, after %d toggles", (uintmax_t)ignored, unit,
+ (uintmax_t)claimed, TOGGLES);
+}
+
+static void
+restore_msr(int fd, const char *unit, uint32_t msr, uint64_t saved, int *bad)
+{
+ uint64_t val;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ val = saved;
+ if (msr_rw(fd, CPUCTL_WRMSR, msr, &val) == 0 &&
+ msr_rw(fd, CPUCTL_RDMSR, msr, &val) == 0 && val == saved)
+ return;
+ }
+ warnx("cannot restore the IBS %s control of CPU 0", unit);
+ (*bad)++;
+}
+
+static void
+cleanup(void)
+{
+ struct stray_state st;
+ int bad, fd, i, v;
+ size_t len;
+ FILE *f;
+
+ f = fopen(STATE_FILE, "r");
+ if (f == NULL)
+ return; /* skipped before anything was changed */
+ if (fread(&st, sizeof(st), 1, f) != 1) {
+ fclose(f);
+ errx(EXIT_FAILURE, "cannot read the saved state");
+ }
+ fclose(f);
+
+ bad = 0;
+ fd = open("/dev/cpuctl0", O_RDWR);
+ if (fd < 0) {
+ warn("/dev/cpuctl0");
+ bad++;
+ } else {
+ restore_msr(fd, "fetch", MSR_AMD_IBS_FETCH_CTL, st.fetch, &bad);
+ restore_msr(fd, "op", MSR_AMD_IBS_OP_CTL, st.op, &bad);
+ close(fd);
+ }
+ for (i = 0; i < 3; i++) {
+ len = sizeof(v);
+ if (sysctlbyname("machdep.panic_on_nmi", NULL, NULL,
+ &st.panic_on_nmi, sizeof(st.panic_on_nmi)) == 0 &&
+ sysctlbyname("machdep.panic_on_nmi", &v, &len, NULL,
+ 0) == 0 && v == st.panic_on_nmi)
+ break;
+ }
+ if (i == 3) {
+ warnx("cannot restore machdep.panic_on_nmi to %d",
+ st.panic_on_nmi);
+ bad++;
+ }
+ if (bad != 0)
+ errx(EXIT_FAILURE, "state not restored");
+}
+
+ATF_TC_WITH_CLEANUP(raw_op_toggle_claimed);
+ATF_TC_HEAD(raw_op_toggle_claimed, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Toggling IBS op through cpuctl leaves no unclaimed NMI");
+}
+
+ATF_TC_BODY(raw_op_toggle_claimed, tc)
+{
+ toggle("op", MSR_AMD_IBS_OP_CTL, OP_EN | OP_MAXCNT);
+}
+
+ATF_TC_CLEANUP(raw_op_toggle_claimed, tc)
+{
+ cleanup();
+}
+
+ATF_TC_WITH_CLEANUP(raw_fetch_toggle_claimed);
+ATF_TC_HEAD(raw_fetch_toggle_claimed, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Toggling IBS fetch through cpuctl leaves no unclaimed NMI");
+}
+
+ATF_TC_BODY(raw_fetch_toggle_claimed, tc)
+{
+ toggle("fetch", MSR_AMD_IBS_FETCH_CTL, FETCH_EN | FETCH_MAXCNT);
+}
+
+ATF_TC_CLEANUP(raw_fetch_toggle_claimed, tc)
+{
+ cleanup();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, raw_op_toggle_claimed);
+ ATF_TP_ADD_TC(tp, raw_fetch_toggle_claimed);
+
+ return (atf_no_error());
+}

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 15, 2:51 AM (9 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41736517
Default Alt Text
D60766.1794711119.diff (8 KB)

Event Timeline