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