xref: /freebsd/tests/sys/pmc/pmc_wrap_test.c (revision 2cfd82f747c04f68f679824ba627460e87ab3848)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2026 Alexander Leidinger <netchild@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other advertising materials provided with the
14  *    distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 /*
30  * Regression tests for process-mode counting PMCs whose accumulated
31  * count is close to, or beyond, the range of the underlying hardware
32  * counter.  Hardware counters are narrower than the 64-bit software
33  * counter (48 bits on modern x86), and the increment collected when a
34  * counter is read out (at context switch and at process exit) must be
35  * computed modulo the hardware width.
36  *
37  * Before the fix these tests panicked INVARIANTS kernels with
38  * "[pmc,...] negative increment" once the hardware counter wrapped,
39  * and silently corrupted the accumulated count on other kernels.
40  *
41  * The counter is attached to a short-lived child process which spins
42  * to advance it; the child exits before the PMC is released, so the
43  * accounting is torn down cleanly (the accumulation is collected on
44  * the exit path, which is one of the two sites the fix touches).
45  *
46  * The tests need a hardware counting event whose counter is narrower
47  * than 64 bits; they skip on systems without one (e.g. VMs without a
48  * vPMU, or with hwpmc(4) not loaded).
49  */
50 
51 #include <sys/types.h>
52 #include <sys/wait.h>
53 
54 #include <errno.h>
55 #include <pmc.h>
56 #include <stdint.h>
57 #include <stdio.h>
58 #include <string.h>
59 #include <unistd.h>
60 
61 #include <atf-c.h>
62 
63 /*
64  * High-rate events; at least one of these should be allocatable on
65  * any x86 or arm64 system with a vPMU.  All of them count while the
66  * child's spin loop runs.
67  */
68 static const char *wrap_test_events[] = {
69 	"instructions",
70 	"cycles",
71 	"branches",
72 	"unhalted-core-cycles",
73 	"inst_retired.any",
74 	"cpu_clk_unhalted.thread",
75 	"ls_not_halted_cyc",
76 	NULL
77 };
78 
79 /* The child generates well more than this many events past the seed. */
80 #define	WRAP_MARGIN	((uint64_t)1 << 20)
81 
82 /* Spin iterations: comfortably more than WRAP_MARGIN events, still < 1s. */
83 #define	SPIN_ITERS	((uint64_t)200 * 1000 * 1000)
84 
85 /* Upper bound on events a single test run can plausibly generate. */
86 #define	SANITY_BOUND	((uint64_t)1 << 40)
87 
88 /*
89  * Child: wait for the parent to start the PMC (one byte on the pipe),
90  * spin to advance the counter across the end of its range, then exit.
91  */
92 static void __attribute__((noinline))
child_spin(int startfd)93 child_spin(int startfd)
94 {
95 	volatile uint64_t sink = 0;
96 	uint64_t i;
97 	char c;
98 
99 	(void)read(startfd, &c, 1);
100 	for (i = 0; i < SPIN_ITERS; i++)
101 		sink += i;
102 	_exit((int)(sink & 0x7f));
103 }
104 
105 /*
106  * Allocate a process-mode counting PMC for the first available event
107  * backed by a counter narrower than 64 bits, attach it to a child,
108  * seed it with the given value, let the child spin past the end of
109  * the counter range, and check that the accumulated count stayed sane.
110  */
111 static void
wrap_test(bool seed_beyond_width)112 wrap_test(bool seed_beyond_width)
113 {
114 	pmc_id_t pmcid;
115 	pmc_value_t final;
116 	uint64_t seed;
117 	uint32_t width;
118 	pid_t child;
119 	int i, pfd[2], status;
120 
121 	if (pmc_init() != 0)
122 		atf_tc_skip("hwpmc(4) is not available: %s", strerror(errno));
123 
124 	width = 0;
125 	pmcid = PMC_ID_INVALID;
126 	for (i = 0; wrap_test_events[i] != NULL; i++) {
127 		if (pmc_allocate(wrap_test_events[i], PMC_MODE_TC, 0,
128 		    PMC_CPU_ANY, &pmcid, 0) != 0)
129 			continue;
130 		ATF_REQUIRE(pmc_width(pmcid, &width) == 0);
131 		if (width >= 32 && width < 64)
132 			break;
133 		ATF_REQUIRE(pmc_release(pmcid) == 0);
134 		pmcid = PMC_ID_INVALID;
135 	}
136 	if (pmcid == PMC_ID_INVALID)
137 		atf_tc_skip("no allocatable counting event with a hardware "
138 		    "counter narrower than 64 bits");
139 
140 	if (seed_beyond_width) {
141 		/* Accumulated count that no longer fits the counter at all. */
142 		seed = ((uint64_t)1 << width) + 12345;
143 	} else {
144 		/* Just below the end of the counter range; the child crosses it. */
145 		seed = ((uint64_t)1 << width) - WRAP_MARGIN;
146 	}
147 
148 	ATF_REQUIRE(pipe(pfd) == 0);
149 	child = fork();
150 	ATF_REQUIRE(child >= 0);
151 	if (child == 0) {
152 		close(pfd[1]);
153 		child_spin(pfd[0]);
154 		/* NOTREACHED */
155 	}
156 	close(pfd[0]);
157 
158 	ATF_REQUIRE(pmc_attach(pmcid, child) == 0);
159 	ATF_REQUIRE(pmc_write(pmcid, seed) == 0);
160 	ATF_REQUIRE(pmc_start(pmcid) == 0);
161 
162 	/* Release the child, which spins and exits. */
163 	ATF_REQUIRE(write(pfd[1], "g", 1) == 1);
164 	close(pfd[1]);
165 	ATF_REQUIRE(waitpid(child, &status, 0) == child);
166 
167 	/*
168 	 * The child is gone: its final increment was collected on the
169 	 * exit path.  Read the accumulated 64-bit count and stop.
170 	 */
171 	ATF_REQUIRE(pmc_read(pmcid, &final) == 0);
172 	(void)pmc_stop(pmcid);
173 
174 	ATF_CHECK_MSG(final >= seed,
175 	    "accumulated count went backwards: seed 0x%jx, final 0x%jx "
176 	    "(width %u)", (uintmax_t)seed, (uintmax_t)final, width);
177 	ATF_CHECK_MSG(final - seed < SANITY_BOUND,
178 	    "accumulated count jumped implausibly: seed 0x%jx, final 0x%jx "
179 	    "(width %u)", (uintmax_t)seed, (uintmax_t)final, width);
180 
181 	ATF_REQUIRE(pmc_release(pmcid) == 0);
182 }
183 
184 ATF_TC(counting_pmc_wraps_hardware_counter);
ATF_TC_HEAD(counting_pmc_wraps_hardware_counter,tc)185 ATF_TC_HEAD(counting_pmc_wraps_hardware_counter, tc)
186 {
187 	atf_tc_set_md_var(tc, "descr",
188 	    "A process-mode counting PMC survives its hardware counter "
189 	    "wrapping around while the counted process runs");
190 	atf_tc_set_md_var(tc, "require.user", "root");
191 }
ATF_TC_BODY(counting_pmc_wraps_hardware_counter,tc)192 ATF_TC_BODY(counting_pmc_wraps_hardware_counter, tc)
193 {
194 	wrap_test(false);
195 }
196 
197 ATF_TC(counting_pmc_beyond_hardware_width);
ATF_TC_HEAD(counting_pmc_beyond_hardware_width,tc)198 ATF_TC_HEAD(counting_pmc_beyond_hardware_width, tc)
199 {
200 	atf_tc_set_md_var(tc, "descr",
201 	    "A process-mode counting PMC keeps counting correctly once "
202 	    "its accumulated count exceeds the hardware counter range");
203 	atf_tc_set_md_var(tc, "require.user", "root");
204 }
ATF_TC_BODY(counting_pmc_beyond_hardware_width,tc)205 ATF_TC_BODY(counting_pmc_beyond_hardware_width, tc)
206 {
207 	wrap_test(true);
208 }
209 
ATF_TP_ADD_TCS(tp)210 ATF_TP_ADD_TCS(tp)
211 {
212 	ATF_TP_ADD_TC(tp, counting_pmc_wraps_hardware_counter);
213 	ATF_TP_ADD_TC(tp, counting_pmc_beyond_hardware_width);
214 
215 	return (atf_no_error());
216 }
217