Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F170561567
D59375.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D59375.id.diff
View Options
diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile
--- a/tests/sys/pmc/Makefile
+++ b/tests/sys/pmc/Makefile
@@ -5,6 +5,7 @@
ATF_TESTS_C+= pmc_detach_test
ATF_TESTS_C+= pmc_group_test
ATF_TESTS_C+= pmc_groupread_test
+ATF_TESTS_C+= pmc_sys_group_test
ATF_TESTS_C+= pmc_group_gate_test
ATF_TESTS_C+= pmc_fork_test
ATF_TESTS_C+= pmc_exec_test
diff --git a/tests/sys/pmc/pmc_sys_group_test.c b/tests/sys/pmc/pmc_sys_group_test.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/pmc/pmc_sys_group_test.c
@@ -0,0 +1,437 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Advanced Micro Devices, Inc.
+ */
+
+/*
+ * Grouped system-mode placement must publish and honor per-CPU row
+ * occupancy. A physical counter must never be live for two owners at
+ * once. The alpha implementation started rows without setting
+ * phw_pmc and without checking it. So a second owner, grouped or
+ * ungrouped, could be placed on top of a live group, with no error and
+ * no race needed.
+ *
+ * An owner is per-process, so each test forks.
+ * The child allocates its own PMCs and so gets its own owner descriptor.
+ * The child reports through its exit status, not through ATF, whose
+ * machinery is not fork-safe.
+ * The test finds row capacity at runtime by shrinking a group until it
+ * commits. A commit-time fit check ignores occupancy, so this measures
+ * pure class capacity.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <pmc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define TEST_EVENT "instructions"
+#define TEST_CPU 0
+
+/* Child exit codes. Anything else is a setup failure. */
+#define CHILD_OK 0 /* Second owner handled correctly. */
+#define CHILD_BUG_PLACED 10 /* Given a row already in use. */
+#define CHILD_BUG_REJECTED 11 /* Refused a free row. */
+#define CHILD_ERR_ALLOCATE 2
+#define CHILD_ERR_CREATE 3
+#define CHILD_ERR_ADD 4
+#define CHILD_ERR_COMMIT 5
+#define CHILD_ERR_START 6
+#define CHILD_ERR_UNGROUPED 7
+
+struct sys_group {
+ uint32_t sg_groupid;
+ pmc_id_t sg_ids[PMC_GROUP_MAX_MEMBERS];
+ u_int sg_nmembers;
+ bool sg_started;
+};
+
+enum group_build_operation {
+ GROUP_BUILD_NONE,
+ GROUP_BUILD_ALLOCATE,
+ GROUP_BUILD_CREATE,
+ GROUP_BUILD_ADD,
+ GROUP_BUILD_COMMIT
+};
+
+struct group_build_failure {
+ enum group_build_operation gbf_operation;
+ u_int gbf_member;
+ int gbf_errno;
+};
+
+static bool
+is_amd(void)
+{
+ char vendor[64];
+ size_t len;
+
+ len = sizeof(vendor);
+ if (sysctlbyname("kern.hwpmc.cpuid", vendor, &len, NULL, 0) != 0)
+ return (false);
+ return (strstr(vendor, "AuthenticAMD") != NULL ||
+ strstr(vendor, "HygonGenuine") != NULL);
+}
+
+static void
+require_hwpmc(void)
+{
+
+ if (geteuid() != 0)
+ atf_tc_skip("system-mode PMCs require root");
+ if (pmc_init() != 0)
+ atf_tc_skip("hwpmc(4) is not available: %s", strerror(errno));
+ if (!is_amd())
+ atf_tc_skip("PMC grouping is supported only on AMD CPUs");
+}
+
+static void
+group_init(struct sys_group *g)
+{
+ u_int i;
+
+ memset(g, 0, sizeof(*g));
+ for (i = 0; i < nitems(g->sg_ids); i++)
+ g->sg_ids[i] = PMC_ID_INVALID;
+}
+
+/*
+ * Releasing a committed leader releases the whole group.
+ * So the remaining members are already gone.
+ * Release them anyway for the uncommitted case.
+ * Ignore the resulting stale-handle errors.
+ */
+static void
+group_teardown(struct sys_group *g)
+{
+ u_int i;
+
+ if (g->sg_started)
+ (void)pmc_stop(g->sg_ids[0]);
+ for (i = 0; i < g->sg_nmembers; i++) {
+ if (g->sg_ids[i] != PMC_ID_INVALID)
+ (void)pmc_release(g->sg_ids[i]);
+ }
+ group_init(g);
+}
+
+/*
+ * Build and commit a system-mode group of nmembers, bound to cpu.
+ * Member 0 is the leader. Return 0, or -1 with errno set.
+ */
+static int
+group_build(struct sys_group *g, int cpu, u_int nmembers,
+ struct group_build_failure *failure)
+{
+ u_int i;
+
+ failure->gbf_operation = GROUP_BUILD_NONE;
+ failure->gbf_member = 0;
+ failure->gbf_errno = 0;
+ group_init(g);
+ g->sg_nmembers = nmembers;
+ for (i = 0; i < nmembers; i++) {
+ if (pmc_allocate_group(TEST_EVENT, PMC_MODE_SC, 0, cpu,
+ &g->sg_ids[i], 0) != 0) {
+ failure->gbf_operation = GROUP_BUILD_ALLOCATE;
+ failure->gbf_member = i;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ }
+ if (pmc_group_create(&g->sg_groupid) != 0) {
+ failure->gbf_operation = GROUP_BUILD_CREATE;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ for (i = 0; i < nmembers; i++) {
+ if (pmc_group_add(g->sg_groupid, g->sg_ids[i], i == 0) != 0) {
+ failure->gbf_operation = GROUP_BUILD_ADD;
+ failure->gbf_member = i;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ }
+ if (pmc_group_commit(g->sg_groupid) != 0) {
+ failure->gbf_operation = GROUP_BUILD_COMMIT;
+ failure->gbf_errno = errno;
+ return (-1);
+ }
+ return (0);
+}
+
+static const char *
+group_build_operation_name(enum group_build_operation operation)
+{
+
+ switch (operation) {
+ case GROUP_BUILD_ALLOCATE:
+ return ("pmc_allocate_group");
+ case GROUP_BUILD_CREATE:
+ return ("pmc_group_create");
+ case GROUP_BUILD_ADD:
+ return ("pmc_group_add");
+ case GROUP_BUILD_COMMIT:
+ return ("pmc_group_commit");
+ case GROUP_BUILD_NONE:
+ default:
+ return ("unknown operation");
+ }
+}
+
+static void
+format_group_build_failure(char *buffer, size_t size, const char *description,
+ const struct group_build_failure *failure)
+{
+ const char *operation;
+
+ operation = group_build_operation_name(failure->gbf_operation);
+ if (failure->gbf_operation == GROUP_BUILD_ALLOCATE ||
+ failure->gbf_operation == GROUP_BUILD_ADD) {
+ snprintf(buffer, size,
+ "%s failed at %s for member %u: errno %d (%s)",
+ description, operation, failure->gbf_member,
+ failure->gbf_errno, strerror(failure->gbf_errno));
+ } else {
+ snprintf(buffer, size, "%s failed at %s: errno %d (%s)",
+ description, operation, failure->gbf_errno,
+ strerror(failure->gbf_errno));
+ }
+}
+
+static int
+child_group_build_error(const struct group_build_failure *failure)
+{
+
+ switch (failure->gbf_operation) {
+ case GROUP_BUILD_ALLOCATE:
+ return (CHILD_ERR_ALLOCATE);
+ case GROUP_BUILD_CREATE:
+ return (CHILD_ERR_CREATE);
+ case GROUP_BUILD_ADD:
+ return (CHILD_ERR_ADD);
+ case GROUP_BUILD_COMMIT:
+ return (CHILD_ERR_COMMIT);
+ case GROUP_BUILD_NONE:
+ default:
+ return (CHILD_ERR_COMMIT);
+ }
+}
+
+/*
+ * Return the largest group that commits on cpu.
+ * This is the class row capacity.
+ * The commit-time fit check ignores occupancy, so this does not
+ * depend on what is currently placed.
+ * Return zero if system-mode groups are unavailable.
+ */
+static u_int
+probe_group_capacity(int cpu)
+{
+ struct group_build_failure failure;
+ struct sys_group probe;
+ char description[128], message[256];
+ u_int n;
+
+ for (n = PMC_GROUP_MAX_MEMBERS; n > 0; n--) {
+ if (group_build(&probe, cpu, n, &failure) == 0) {
+ group_teardown(&probe);
+ return (n);
+ }
+ group_teardown(&probe);
+ snprintf(description, sizeof(description),
+ "capacity probe for %u members", n);
+ if (failure.gbf_operation != GROUP_BUILD_COMMIT ||
+ failure.gbf_errno != ENOSPC) {
+ format_group_build_failure(message, sizeof(message),
+ description, &failure);
+ atf_tc_fail("%s; expected pmc_group_commit to fail with "
+ "ENOSPC while reducing the probe size", message);
+ }
+ }
+ return (0);
+}
+
+static void
+check_child(int status)
+{
+
+ ATF_REQUIRE_MSG(WIFEXITED(status), "second owner died on signal %d",
+ WIFSIGNALED(status) ? WTERMSIG(status) : 0);
+ switch (WEXITSTATUS(status)) {
+ case CHILD_OK:
+ break;
+ case CHILD_BUG_PLACED:
+ atf_tc_fail("second owner was placed on a row already live "
+ "for the first owner");
+ case CHILD_BUG_REJECTED:
+ atf_tc_fail("second owner was refused a free row");
+ case CHILD_ERR_ALLOCATE:
+ atf_tc_fail("second owner setup failed at pmc_allocate_group");
+ case CHILD_ERR_CREATE:
+ atf_tc_fail("second owner setup failed at pmc_group_create");
+ case CHILD_ERR_ADD:
+ atf_tc_fail("second owner setup failed at pmc_group_add");
+ case CHILD_ERR_COMMIT:
+ atf_tc_fail("second owner setup failed at pmc_group_commit; "
+ "commit must ignore current occupancy");
+ case CHILD_ERR_START:
+ atf_tc_fail("second owner pmc_start failed with an errno other "
+ "than ENOSPC");
+ case CHILD_ERR_UNGROUPED:
+ atf_tc_fail("ungrouped pmc_allocate failed with an errno other "
+ "than EINVAL");
+ default:
+ atf_tc_fail("second owner setup failed, exit %d",
+ WEXITSTATUS(status));
+ }
+}
+
+/* Claim the same rows the first owner is already running on. */
+static int
+child_full_collision(u_int cap)
+{
+ struct group_build_failure failure;
+ struct sys_group owner_b;
+ int rc;
+
+ if (group_build(&owner_b, TEST_CPU, cap, &failure) != 0) {
+ rc = child_group_build_error(&failure);
+ group_teardown(&owner_b);
+ return (rc);
+ }
+ errno = 0;
+ if (pmc_start(owner_b.sg_ids[0]) == 0) {
+ owner_b.sg_started = true;
+ rc = CHILD_BUG_PLACED;
+ } else
+ rc = errno == ENOSPC ? CHILD_OK : CHILD_ERR_START;
+ group_teardown(&owner_b);
+ return (rc);
+}
+
+/* Make the same claim through the ungrouped allocation path. */
+static int
+child_ungrouped_alloc(u_int cap __unused)
+{
+ pmc_id_t id;
+
+ errno = 0;
+ if (pmc_allocate(TEST_EVENT, PMC_MODE_SC, 0, TEST_CPU, &id, 0) != 0)
+ return (errno == EINVAL ? CHILD_OK : CHILD_ERR_UNGROUPED);
+ (void)pmc_release(id);
+ return (CHILD_BUG_PLACED);
+}
+
+/* Claim the one row the first owner left free on purpose. */
+static int
+child_single_member(u_int cap __unused)
+{
+ struct group_build_failure failure;
+ struct sys_group owner_b;
+ int rc;
+
+ if (group_build(&owner_b, TEST_CPU, 1, &failure) != 0)
+ rc = child_group_build_error(&failure);
+ else if (pmc_start(owner_b.sg_ids[0]) != 0)
+ rc = CHILD_BUG_REJECTED;
+ else {
+ owner_b.sg_started = true;
+ rc = CHILD_OK;
+ }
+ group_teardown(&owner_b);
+ return (rc);
+}
+
+/*
+ * Run fn as a second owner while the first owner holds nrows on
+ * TEST_CPU. Check how the kernel answered it.
+ */
+static void
+run_second_owner(u_int nrows, int (*fn)(u_int), u_int arg)
+{
+ struct group_build_failure failure;
+ struct sys_group owner_a;
+ char message[256];
+ pid_t pid;
+ int status;
+
+ if (group_build(&owner_a, TEST_CPU, nrows, &failure) != 0) {
+ format_group_build_failure(message, sizeof(message),
+ "first owner setup", &failure);
+ group_teardown(&owner_a);
+ atf_tc_fail("%s", message);
+ }
+ ATF_REQUIRE_MSG(pmc_start(owner_a.sg_ids[0]) == 0,
+ "first owner start failed: %s", strerror(errno));
+ owner_a.sg_started = true;
+
+ pid = fork();
+ ATF_REQUIRE(pid != -1);
+ if (pid == 0)
+ _exit(fn(arg));
+ ATF_REQUIRE(waitpid(pid, &status, 0) == pid);
+
+ group_teardown(&owner_a);
+ check_child(status);
+}
+
+ATF_TC_WITHOUT_HEAD(sys_two_owners_full_collision);
+ATF_TC_BODY(sys_two_owners_full_collision, tc)
+{
+ u_int cap;
+
+ require_hwpmc();
+ cap = probe_group_capacity(TEST_CPU);
+ if (cap == 0)
+ atf_tc_skip("no system-mode group capacity on CPU %d",
+ TEST_CPU);
+ run_second_owner(cap, child_full_collision, cap);
+}
+
+ATF_TC_WITHOUT_HEAD(sys_grouped_vs_ungrouped_alloc);
+ATF_TC_BODY(sys_grouped_vs_ungrouped_alloc, tc)
+{
+ u_int cap;
+
+ require_hwpmc();
+ cap = probe_group_capacity(TEST_CPU);
+ if (cap == 0)
+ atf_tc_skip("no system-mode group capacity on CPU %d",
+ TEST_CPU);
+ run_second_owner(cap, child_ungrouped_alloc, 0);
+}
+
+ATF_TC_WITHOUT_HEAD(sys_two_owners_disjoint_rows);
+ATF_TC_BODY(sys_two_owners_disjoint_rows, tc)
+{
+ u_int cap;
+
+ require_hwpmc();
+ cap = probe_group_capacity(TEST_CPU);
+ if (cap < 2)
+ atf_tc_skip("need at least two system-mode group rows on "
+ "CPU %d, have %u", TEST_CPU, cap);
+ run_second_owner(cap - 1, child_single_member, 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, sys_two_owners_full_collision);
+ ATF_TP_ADD_TC(tp, sys_grouped_vs_ungrouped_alloc);
+ ATF_TP_ADD_TC(tp, sys_two_owners_disjoint_rows);
+
+ return (atf_no_error());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Sep 6, 10:21 AM (7 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38285974
Default Alt Text
D59375.id.diff (11 KB)
Attached To
Mode
D59375: tests: cover system-mode PMC group placement
Attached
Detach File
Event Timeline
Log In to Comment