From nobody Mon Aug 3 17:31:16 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hDNxw4Vprz6mbwt for ; Mon, 03 Aug 2026 17:31:16 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hDNxw1Xy2z3n80 for ; Mon, 03 Aug 2026 17:31:16 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1785778276; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ytC1Ey0XCvVh8QlhS+oynPmdCjdG+fL0pKKJR2G3i30=; b=WCSzDMAtRhh5P0jodpD7cyrSfc+nrYQBlx3L5aZ+O+dU63yTEb/NcjYILLp9eV/ElDnrcI tOKRHduWKUfQirL2xYlAe9hQzcXFwVlXcbRrqmR9tEyew32zJ9pYx7Qon/ynJUT9pDT7k9 Of/UYFeWCnp+yQec1H8QGSPb8rrKb4cVx4QsN32BhIz5bwWJv0SLngR3aRUu8akndXofQE DA57pUtFqRYQq6xA/xpjCPdwIYiKuMgS5ytVdCwXVtyaIqNB2IGezfbKllxYa9BNcdZEr9 mUqc/4YCo1fb1pFlYW3eVj/UqxV5oZBLnRb3BdyEpI+s4QRTrWLi/G1/pyd8Cw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1785778276; a=rsa-sha256; cv=none; b=ABlR8oEUF7KI0n0oLdKQDvBg5zEDvKkjuYIZPe1Nsgw4Dm8Y5nfFGsTovUaa11oLVb3ZJ4 0U9QGOQ0y1M1PDgz4jShXrkw0lIVSifZAfasMifiU9xpgTKKU5nNPk2Of0WbDUkw2lcjKZ Q23xzrNGZ5hzA1pPJ2Zt0OkI7Q6OEBXzSWcw52jEytI7J8kwndzlryQQ+0Q078MELbuL7k c7mZNblGJg9h375RIdeliNzUZOWn4G4+Megn4xEk+deNZjCBriNZgXBEUwHXQyVLnLwKmR QYX6GLRPaZDEQ1E9w3irTUKb/1SWIm7H/7DBXt0jc0Bp3C1CvaCpata2VhgF1g== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1785778276; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ytC1Ey0XCvVh8QlhS+oynPmdCjdG+fL0pKKJR2G3i30=; b=RxrGNhs2vaQszSRcElTINx4SexHkWFuDrw3FHAJrq3Jys/Zr0HYmhnBN65QT1XTtoOQQp9 pV9WKIgxou3a+pxCW4JT9CLm+ITYfYu/mot3xJ5KKhk3DU70Mm6XbD/TsDMHeI9o9oTdeL 0Ybh9uVwTP1301qTWuqQSUr3bxwEbF1DmExDdaafye3SHSOECgpVk3IoPinawMPw+dQcFy FgoRsp8vQeTeq28GKjkPisFs6hmfjsvuqcMkijwm2O32+gftqqGv4ZkWAsGHmKuL62IhGX ig2CGurRhyHPMajZpvVYsl0aCWvebTIC3j3MFYLSODam5Gk635iKUY90klvWHQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hDNxw0ZGnz19H6 for ; Mon, 03 Aug 2026 17:31:16 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 19281 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 03 Aug 2026 17:31:16 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Alexander Leidinger Subject: git: ac3d63e50862 - stable/14 - hwpmc: add regression tests for counting-PMC counter wraparound List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: netchild X-Git-Repository: src X-Git-Refname: refs/heads/stable/14 X-Git-Reftype: branch X-Git-Commit: ac3d63e50862b6031feb839932c88b1a4c5a5c1f Auto-Submitted: auto-generated Date: Mon, 03 Aug 2026 17:31:16 +0000 Message-Id: <6a70d064.19281.710d0c08@gitrepo.freebsd.org> The branch stable/14 has been updated by netchild: URL: https://cgit.FreeBSD.org/src/commit/?id=ac3d63e50862b6031feb839932c88b1a4c5a5c1f commit ac3d63e50862b6031feb839932c88b1a4c5a5c1f Author: Alexander Leidinger AuthorDate: 2026-07-19 08:08:48 +0000 Commit: Alexander Leidinger CommitDate: 2026-08-03 17:31:02 +0000 hwpmc: add regression tests for counting-PMC counter wraparound Exercise a process-mode counting PMC whose accumulated count crosses, or already exceeds, the range of the underlying hardware counter. Before the previous commit, the first context switch after the hardware counter wrapped panicked INVARIANTS kernels with "negative increment" and silently corrupted the accumulated count on other kernels. The tests need a hardware counting event backed by a counter narrower than 64 bits and skip where none is available (hwpmc(4) not loaded, or a VM without a vPMU). Reviewed by: adrian MFC after: 2 weeks Assisted-by: Claude Code (Fable 5) Differential Revision: https://reviews.freebsd.org/D58341 (cherry picked from commit 2cfd82f747c04f68f679824ba627460e87ab3848) --- etc/mtree/BSD.tests.dist | 2 + tests/sys/Makefile | 1 + tests/sys/pmc/Makefile | 7 ++ tests/sys/pmc/pmc_wrap_test.c | 216 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist index 1df5af7dccf4..41944b275fcc 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -923,6 +923,8 @@ utimensat .. .. + pmc + .. posixshm .. sound diff --git a/tests/sys/Makefile b/tests/sys/Makefile index 8fa5844fc47b..7b7a97fd1359 100644 --- a/tests/sys/Makefile +++ b/tests/sys/Makefile @@ -30,6 +30,7 @@ TESTS_SUBDIRS+= netlink TESTS_SUBDIRS+= netmap TESTS_SUBDIRS+= netpfil TESTS_SUBDIRS+= opencrypto +TESTS_SUBDIRS+= pmc TESTS_SUBDIRS+= posixshm TESTS_SUBDIRS+= ses TESTS_SUBDIRS+= sound diff --git a/tests/sys/pmc/Makefile b/tests/sys/pmc/Makefile new file mode 100644 index 000000000000..403acd0311ce --- /dev/null +++ b/tests/sys/pmc/Makefile @@ -0,0 +1,7 @@ +TESTSDIR= ${TESTSBASE}/sys/pmc + +ATF_TESTS_C= pmc_wrap_test + +LIBADD+= pmc + +.include diff --git a/tests/sys/pmc/pmc_wrap_test.c b/tests/sys/pmc/pmc_wrap_test.c new file mode 100644 index 000000000000..58e7b3ef416a --- /dev/null +++ b/tests/sys/pmc/pmc_wrap_test.c @@ -0,0 +1,216 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alexander Leidinger + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other advertising materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Regression tests for process-mode counting PMCs whose accumulated + * count is close to, or beyond, the range of the underlying hardware + * counter. Hardware counters are narrower than the 64-bit software + * counter (48 bits on modern x86), and the increment collected when a + * counter is read out (at context switch and at process exit) must be + * computed modulo the hardware width. + * + * Before the fix these tests panicked INVARIANTS kernels with + * "[pmc,...] negative increment" once the hardware counter wrapped, + * and silently corrupted the accumulated count on other kernels. + * + * The counter is attached to a short-lived child process which spins + * to advance it; the child exits before the PMC is released, so the + * accounting is torn down cleanly (the accumulation is collected on + * the exit path, which is one of the two sites the fix touches). + * + * The tests need a hardware counting event whose counter is narrower + * than 64 bits; they skip on systems without one (e.g. VMs without a + * vPMU, or with hwpmc(4) not loaded). + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* + * High-rate events; at least one of these should be allocatable on + * any x86 or arm64 system with a vPMU. All of them count while the + * child's spin loop runs. + */ +static const char *wrap_test_events[] = { + "instructions", + "cycles", + "branches", + "unhalted-core-cycles", + "inst_retired.any", + "cpu_clk_unhalted.thread", + "ls_not_halted_cyc", + NULL +}; + +/* The child generates well more than this many events past the seed. */ +#define WRAP_MARGIN ((uint64_t)1 << 20) + +/* Spin iterations: comfortably more than WRAP_MARGIN events, still < 1s. */ +#define SPIN_ITERS ((uint64_t)200 * 1000 * 1000) + +/* Upper bound on events a single test run can plausibly generate. */ +#define SANITY_BOUND ((uint64_t)1 << 40) + +/* + * Child: wait for the parent to start the PMC (one byte on the pipe), + * spin to advance the counter across the end of its range, then exit. + */ +static void __attribute__((noinline)) +child_spin(int startfd) +{ + volatile uint64_t sink = 0; + uint64_t i; + char c; + + (void)read(startfd, &c, 1); + for (i = 0; i < SPIN_ITERS; i++) + sink += i; + _exit((int)(sink & 0x7f)); +} + +/* + * Allocate a process-mode counting PMC for the first available event + * backed by a counter narrower than 64 bits, attach it to a child, + * seed it with the given value, let the child spin past the end of + * the counter range, and check that the accumulated count stayed sane. + */ +static void +wrap_test(bool seed_beyond_width) +{ + pmc_id_t pmcid; + pmc_value_t final; + uint64_t seed; + uint32_t width; + pid_t child; + int i, pfd[2], status; + + if (pmc_init() != 0) + atf_tc_skip("hwpmc(4) is not available: %s", strerror(errno)); + + width = 0; + pmcid = PMC_ID_INVALID; + for (i = 0; wrap_test_events[i] != NULL; i++) { + if (pmc_allocate(wrap_test_events[i], PMC_MODE_TC, 0, + PMC_CPU_ANY, &pmcid, 0) != 0) + continue; + ATF_REQUIRE(pmc_width(pmcid, &width) == 0); + if (width >= 32 && width < 64) + break; + ATF_REQUIRE(pmc_release(pmcid) == 0); + pmcid = PMC_ID_INVALID; + } + if (pmcid == PMC_ID_INVALID) + atf_tc_skip("no allocatable counting event with a hardware " + "counter narrower than 64 bits"); + + if (seed_beyond_width) { + /* Accumulated count that no longer fits the counter at all. */ + seed = ((uint64_t)1 << width) + 12345; + } else { + /* Just below the end of the counter range; the child crosses it. */ + seed = ((uint64_t)1 << width) - WRAP_MARGIN; + } + + ATF_REQUIRE(pipe(pfd) == 0); + child = fork(); + ATF_REQUIRE(child >= 0); + if (child == 0) { + close(pfd[1]); + child_spin(pfd[0]); + /* NOTREACHED */ + } + close(pfd[0]); + + ATF_REQUIRE(pmc_attach(pmcid, child) == 0); + ATF_REQUIRE(pmc_write(pmcid, seed) == 0); + ATF_REQUIRE(pmc_start(pmcid) == 0); + + /* Release the child, which spins and exits. */ + ATF_REQUIRE(write(pfd[1], "g", 1) == 1); + close(pfd[1]); + ATF_REQUIRE(waitpid(child, &status, 0) == child); + + /* + * The child is gone: its final increment was collected on the + * exit path. Read the accumulated 64-bit count and stop. + */ + ATF_REQUIRE(pmc_read(pmcid, &final) == 0); + (void)pmc_stop(pmcid); + + ATF_CHECK_MSG(final >= seed, + "accumulated count went backwards: seed 0x%jx, final 0x%jx " + "(width %u)", (uintmax_t)seed, (uintmax_t)final, width); + ATF_CHECK_MSG(final - seed < SANITY_BOUND, + "accumulated count jumped implausibly: seed 0x%jx, final 0x%jx " + "(width %u)", (uintmax_t)seed, (uintmax_t)final, width); + + ATF_REQUIRE(pmc_release(pmcid) == 0); +} + +ATF_TC(counting_pmc_wraps_hardware_counter); +ATF_TC_HEAD(counting_pmc_wraps_hardware_counter, tc) +{ + atf_tc_set_md_var(tc, "descr", + "A process-mode counting PMC survives its hardware counter " + "wrapping around while the counted process runs"); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(counting_pmc_wraps_hardware_counter, tc) +{ + wrap_test(false); +} + +ATF_TC(counting_pmc_beyond_hardware_width); +ATF_TC_HEAD(counting_pmc_beyond_hardware_width, tc) +{ + atf_tc_set_md_var(tc, "descr", + "A process-mode counting PMC keeps counting correctly once " + "its accumulated count exceeds the hardware counter range"); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(counting_pmc_beyond_hardware_width, tc) +{ + wrap_test(true); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, counting_pmc_wraps_hardware_counter); + ATF_TP_ADD_TC(tp, counting_pmc_beyond_hardware_width); + + return (atf_no_error()); +}