From nobody Mon Jul 20 10:25:15 2026 X-Original-To: dev-commits-src-all@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 4h3c8q2r6pz6ljr3 for ; Mon, 20 Jul 2026 10:25:15 +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 4h3c8q2Dtkz4557 for ; Mon, 20 Jul 2026 10:25:15 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784543115; 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=BB7U7Tna5rq2BRGYhqLZ5kcdC7TYI3DJNcd09I1IfmI=; b=gVnz3uCiScce/Iv8XNJ8hVEHNFJqc3jzt/IM3lbw1YaRAu6DxwOxeUxxJdSx5OiHBOP17L yKP71T7fjTS3VmQqjUsuYKGe0uJ2moMuRIEmiMCxbO350yxTIA8kETK2PS1W5JZawOgvmM 6Xp/6GMYyRwdNRgeoygYbg1/GOcqRscVsgoEmYbZsZH2mj3nUGIwjq9xWJLrjHiYMDthmV holz9vCYLhKVrpjABqC78wz0ZlefjEE8+motttzOJDKcNn1wbXSqukDl81qbNX6K2mRVPx 4PqsvJkPPlfVrNzB5qy/mhJ0ULqoHz7kkaGrbyJXM5NmK4opkmWPBAphzh4UmA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1784543115; a=rsa-sha256; cv=none; b=oaGmVFibs2mWF7TxvPdQQ2ZiP0jeS+WYSE5r4VbrOg1VGiyC+ikKzfl8KlknMCY5KzRn2C VOL5vhCSntJ9IBsuE1giEwIwlr5nWsZSyltfm22kUU5DchgTJHOQtNrEyIt3uu59YRkOHO Ip1FkeJJwHBl6pTH4VhSQsKPzCEQsNmwFiSMypP9KpC3C2K3TqeFfj+VrG4gDBxkgKfj0T xfJju/nxvygDhcvTdK7u03OnoiLlE8GJ5K7UYw/GSPw6/GoPhryeLh/QgKj+gWjYmANGqS dIAnMh4ojir0mKTUFKDf+qvFCgKdjw+O9IRFwP6/u+tBG3iXrL9hybwpQfH5mQ== 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=1784543115; 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=BB7U7Tna5rq2BRGYhqLZ5kcdC7TYI3DJNcd09I1IfmI=; b=Ih8N9Ao0z0Gu/Xovj7HBslC0q9Lg6D9xXggefD+ue17s9sKtozEeISgHXC1pjA9Hr0nE9w NBmcP6VcG9AkMnzCnlk2KD32Y7CLLQKX3lE107rnE/jXJzlUlj1n17MTxc1DURY3iH/YfR JjtJpeELvpyfNjKTwob7MX0J7N1RrAh8KgSkY/JCwNkazfrFZ0jg3R/k96CUToHgGUB1ts 9S1jTEtjF8zCWOQjRmJFuUyTS2IAXedvDCoKK90FuCsen5yB5xFwwXKLXMah1JbYs6++P5 bzEFHF0/Ilka7hX+//bPmC8K1tvRpjyyhNBFtrv5MqVhjD6CJgI4o9NhoPwfCQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h3c8q0mZ1zxxg for ; Mon, 20 Jul 2026 10:25:15 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 314a2 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 20 Jul 2026 10:25:15 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Alexander Leidinger Subject: git: 2cfd82f747c0 - main - hwpmc: add regression tests for counting-PMC counter wraparound List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@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/main X-Git-Reftype: branch X-Git-Commit: 2cfd82f747c04f68f679824ba627460e87ab3848 Auto-Submitted: auto-generated Date: Mon, 20 Jul 2026 10:25:15 +0000 Message-Id: <6a5df78b.314a2.2f5ef495@gitrepo.freebsd.org> The branch main has been updated by netchild: URL: https://cgit.FreeBSD.org/src/commit/?id=2cfd82f747c04f68f679824ba627460e87ab3848 commit 2cfd82f747c04f68f679824ba627460e87ab3848 Author: Alexander Leidinger AuthorDate: 2026-07-19 08:08:48 +0000 Commit: Alexander Leidinger CommitDate: 2026-07-20 10:24:36 +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 --- 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 004be9536f1f..dec5bf53ebeb 100644 --- a/etc/mtree/BSD.tests.dist +++ b/etc/mtree/BSD.tests.dist @@ -971,6 +971,8 @@ utimensat .. .. + pmc + .. posixshm .. ses diff --git a/tests/sys/Makefile b/tests/sys/Makefile index ccdc29886d23..7e7f35ded73f 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()); +}