xref: /freebsd/tests/sys/pmc/pmc_lifecycle_test.c (revision d00da14532bc4408f3e66535c88276d00905af7c)
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  * @file
31  * Tests for hwpmc(4)'s process-attachment lifecycle: what happens as a PMC
32  * is attached, inherited, and torn down, especially when the teardown does
33  * not happen in the tidy order the common path assumes.
34  *
35  * The orderings driven here are a target that exits before it is
36  * detached, an owner that exits before its target (which reaches the
37  * REMOVE side of pmc_find_process_descriptor()),
38  * a release of a still-running attached PMC, row exhaustion released out
39  * of order, and PMC_F_DESCENDANTS, where the teardown has to account for a
40  * target the owner never attached by hand.
41  *
42  * All PMCs are SOFT-class, so nothing here needs a hardware counter.
43  */
44 
45 #include <sys/param.h>
46 #include <sys/wait.h>
47 
48 #include <errno.h>
49 #include <pmc.h>
50 #include <signal.h>
51 #include <stdint.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56 
57 #include <atf-c.h>
58 
59 static void
require_hwpmc(void)60 require_hwpmc(void)
61 {
62 
63 	if (pmc_init() != 0)
64 		atf_tc_skip("hwpmc(4) is not available");
65 }
66 
67 /**
68  * @internal
69  * Allocate the first SOFT event this mode and flag set accept.  Which event
70  * it is does not matter; that one exists is what the case needs.
71  */
72 static pmc_id_t
allocate_soft_pmc_flags(enum pmc_mode mode,uint32_t flags)73 allocate_soft_pmc_flags(enum pmc_mode mode, uint32_t flags)
74 {
75 	const char **names;
76 	char spec[128];
77 	pmc_id_t id;
78 	int nnames, i;
79 
80 	if (pmc_event_names_of_class(PMC_CLASS_SOFT, &names, &nnames) != 0)
81 		return (PMC_ID_INVALID);
82 	for (i = 0; i < nnames; i++) {
83 		(void)snprintf(spec, sizeof(spec), "SOFT-%s", names[i]);
84 		if (pmc_allocate(spec, mode, flags, PMC_CPU_ANY, &id, 0) == 0)
85 			return (id);
86 	}
87 	return (PMC_ID_INVALID);
88 }
89 
90 static pmc_id_t
require_soft_pmc(enum pmc_mode mode)91 require_soft_pmc(enum pmc_mode mode)
92 {
93 	pmc_id_t id;
94 
95 	if ((id = allocate_soft_pmc_flags(mode, 0)) == PMC_ID_INVALID)
96 		atf_tc_skip("no SOFT-class PMC is allocatable");
97 	return (id);
98 }
99 
100 /**
101  * @internal
102  * A child that does nothing until killed, for use as an attach target.
103  */
104 static pid_t
spawn_idle_child(void)105 spawn_idle_child(void)
106 {
107 	pid_t pid;
108 
109 	pid = fork();
110 	if (pid == 0) {
111 		for (;;)
112 			(void)pause();
113 	}
114 	return (pid);
115 }
116 
117 static void
reap(pid_t pid)118 reap(pid_t pid)
119 {
120 	int status;
121 
122 	(void)kill(pid, SIGKILL);
123 	(void)waitpid(pid, &status, 0);
124 }
125 
126 /**
127  * @internal
128  * The bookkeeping around attach and detach.
129  */
130 ATF_TC_WITHOUT_HEAD(attach_detach_bookkeeping);
ATF_TC_BODY(attach_detach_bookkeeping,tc)131 ATF_TC_BODY(attach_detach_bookkeeping, tc)
132 {
133 	pmc_id_t id;
134 	pid_t other;
135 
136 	require_hwpmc();
137 	id = require_soft_pmc(PMC_MODE_TC);
138 	other = spawn_idle_child();
139 	ATF_REQUIRE(other > 0);
140 
141 	ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
142 	    strerror(errno));
143 
144 	/* A second attach of the same target must be refused, not doubled. */
145 	errno = 0;
146 	ATF_CHECK_MSG(pmc_attach(id, getpid()) != 0,
147 	    "a second attach of the same target succeeded");
148 
149 	/* Detaching a pid that was never attached must be refused. */
150 	errno = 0;
151 	ATF_CHECK_MSG(pmc_detach(id, other) != 0,
152 	    "detached a target that was never attached");
153 
154 	/* One detach undoes the one attach. */
155 	ATF_CHECK_MSG(pmc_detach(id, getpid()) == 0, "pmc_detach: %s",
156 	    strerror(errno));
157 
158 	/* A second detach must now be refused. */
159 	errno = 0;
160 	ATF_CHECK_MSG(pmc_detach(id, getpid()) != 0,
161 	    "a second detach of the same target succeeded");
162 
163 	ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s",
164 	    strerror(errno));
165 	reap(other);
166 }
167 
168 /**
169  * @internal
170  * Detach a target that has already exited and been reaped.  The
171  * kernel's own eventhandler removes an exiting process's descriptor, so by
172  * the time we detach there is nothing there; it must say so cleanly, and
173  * the PMC must still release.
174  */
175 ATF_TC_WITHOUT_HEAD(detach_after_target_reaped);
ATF_TC_BODY(detach_after_target_reaped,tc)176 ATF_TC_BODY(detach_after_target_reaped, tc)
177 {
178 	pmc_id_t id;
179 	pid_t target;
180 
181 	require_hwpmc();
182 	id = require_soft_pmc(PMC_MODE_TC);
183 	target = spawn_idle_child();
184 	ATF_REQUIRE(target > 0);
185 
186 	ATF_REQUIRE_MSG(pmc_attach(id, target) == 0, "pmc_attach: %s",
187 	    strerror(errno));
188 
189 	/* Kill and fully reap the target before detaching. */
190 	reap(target);
191 
192 	errno = 0;
193 	ATF_CHECK_MSG(pmc_detach(id, target) != 0,
194 	    "detached a target that had exited and been reaped");
195 
196 	ATF_CHECK_MSG(pmc_release(id) == 0, "pmc_release: %s",
197 	    strerror(errno));
198 }
199 
200 /**
201  * @internal
202  * Release a PMC that is still attached and still running, with no
203  * prior detach or stop: the release path has to reclaim the reference
204  * the running PMC still holds rather than wedge.
205  */
206 ATF_TC_WITHOUT_HEAD(release_running_attached);
ATF_TC_BODY(release_running_attached,tc)207 ATF_TC_BODY(release_running_attached, tc)
208 {
209 	pmc_id_t id;
210 
211 	require_hwpmc();
212 	id = require_soft_pmc(PMC_MODE_TC);
213 
214 	ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
215 	    strerror(errno));
216 	ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
217 
218 	/* Run a little, so the PMC is actually loaded on a CPU. */
219 	{
220 		volatile unsigned long s = 0;
221 		int i;
222 
223 		for (i = 0; i < 5000000; i++)
224 			s += i;
225 	}
226 	(void)usleep(100000);
227 
228 	/* Straight to release: no stop, no detach. */
229 	ATF_CHECK_MSG(pmc_release(id) == 0,
230 	    "release of a running, attached PMC failed: %s", strerror(errno));
231 
232 	/* The driver is still usable afterwards. */
233 	{
234 		pmc_id_t again = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
235 
236 		ATF_CHECK_MSG(again != PMC_ID_INVALID,
237 		    "hwpmc is unusable after the release");
238 		if (again != PMC_ID_INVALID)
239 			(void)pmc_release(again);
240 	}
241 }
242 
243 /**
244  * @internal
245  * The owner exits before the target.  A child process is the owner:
246  * it allocates a PMC and attaches it to a sibling that outlives it, then
247  * exits without releasing.  The kernel tears the owner's PMCs down through
248  * the REMOVE side of pmc_find_process_descriptor() - the other of its two
249  * unlink mechanisms, which no test exercised.  The parent then confirms
250  * the driver is still usable and the target still alive.
251  */
252 ATF_TC_WITHOUT_HEAD(owner_exit_before_target);
ATF_TC_BODY(owner_exit_before_target,tc)253 ATF_TC_BODY(owner_exit_before_target, tc)
254 {
255 	pmc_id_t id;
256 	pid_t target, owner;
257 	int status;
258 
259 	require_hwpmc();
260 
261 	target = spawn_idle_child();
262 	ATF_REQUIRE(target > 0);
263 
264 	owner = fork();
265 	ATF_REQUIRE(owner >= 0);
266 	if (owner == 0) {
267 		/* Owner: attach to the sibling target, then just exit. */
268 		if (pmc_init() != 0)
269 			_exit(10);
270 		id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
271 		if (id == PMC_ID_INVALID)
272 			_exit(11);
273 		if (pmc_attach(id, target) != 0)
274 			_exit(12);
275 		if (pmc_start(id) != 0)
276 			_exit(13);
277 		/* Exit with the PMC still allocated, attached and running. */
278 		_exit(0);
279 	}
280 
281 	ATF_REQUIRE(waitpid(owner, &status, 0) == owner);
282 	ATF_REQUIRE_MSG(WIFEXITED(status),
283 	    "the owner did not exit normally (it may have panicked the "
284 	    "kernel; status 0x%x)", status);
285 	ATF_REQUIRE_MSG(WEXITSTATUS(status) == 0,
286 	    "the owner's setup failed (exit %d): 10=init 11=alloc 12=attach "
287 	    "13=start", WEXITSTATUS(status));
288 
289 	/*
290 	 * The owner is gone.  Its descriptor teardown must have run without
291 	 * wedging: prove the driver still works and the target is still here.
292 	 */
293 	id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
294 	ATF_CHECK_MSG(id != PMC_ID_INVALID,
295 	    "hwpmc is unusable after the owner exited with a live target");
296 	if (id != PMC_ID_INVALID) {
297 		ATF_CHECK(pmc_attach(id, getpid()) == 0);
298 		ATF_CHECK(pmc_detach(id, getpid()) == 0);
299 		ATF_CHECK(pmc_release(id) == 0);
300 	}
301 	ATF_CHECK_MSG(kill(target, 0) == 0,
302 	    "the target did not survive the owner's exit");
303 	reap(target);
304 }
305 
306 /**
307  * @internal
308  * Allocate SOFT PMCs until the rows are exhausted, then release them
309  * in an order other than allocation order.  Exercises the row free-list
310  * against out-of-order frees.
311  */
312 ATF_TC_WITHOUT_HEAD(exhaust_then_release_reverse);
ATF_TC_BODY(exhaust_then_release_reverse,tc)313 ATF_TC_BODY(exhaust_then_release_reverse, tc)
314 {
315 	pmc_id_t ids[64];
316 	int n, i;
317 
318 	require_hwpmc();
319 
320 	n = 0;
321 	while (n < (int)nitems(ids)) {
322 		pmc_id_t id = allocate_soft_pmc_flags(PMC_MODE_TC, 0);
323 
324 		if (id == PMC_ID_INVALID)
325 			break;
326 		ids[n++] = id;
327 	}
328 	ATF_REQUIRE_MSG(n > 0, "not one SOFT PMC could be allocated");
329 
330 	/* At least one allocation should have failed at exhaustion. */
331 	ATF_CHECK_MSG(n < (int)nitems(ids),
332 	    "allocation never hit a limit in %zu tries", nitems(ids));
333 
334 	/* Release in reverse order; every one must succeed. */
335 	for (i = n - 1; i >= 0; i--)
336 		ATF_CHECK_MSG(pmc_release(ids[i]) == 0,
337 		    "reverse release of id 0x%08x (slot %d) failed: %s",
338 		    ids[i], i, strerror(errno));
339 }
340 
341 /**
342  * @internal
343  * A PMC_F_DESCENDANTS counter, whose child inherits the attachment.
344  * The owner attaches to itself with descendants set, forks a child that
345  * does a little work and exits, reaps it, and releases.  The teardown has
346  * to account for the child the owner never attached to by hand.
347  */
348 ATF_TC_WITHOUT_HEAD(descendants_inherit_and_release);
ATF_TC_BODY(descendants_inherit_and_release,tc)349 ATF_TC_BODY(descendants_inherit_and_release, tc)
350 {
351 	pmc_id_t id;
352 	pid_t child;
353 	int status;
354 
355 	require_hwpmc();
356 	id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS);
357 	if (id == PMC_ID_INVALID)
358 		atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS");
359 
360 	ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
361 	    strerror(errno));
362 	ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
363 
364 	child = fork();
365 	ATF_REQUIRE(child >= 0);
366 	if (child == 0) {
367 		volatile unsigned long s = 0;
368 		int i;
369 
370 		for (i = 0; i < 2000000; i++)
371 			s += i;
372 		_exit(0);
373 	}
374 	ATF_REQUIRE(waitpid(child, &status, 0) == child);
375 	ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
376 	    "the descendant did not exit cleanly (status 0x%x)", status);
377 
378 	ATF_CHECK(pmc_stop(id) == 0);
379 	ATF_CHECK_MSG(pmc_release(id) == 0,
380 	    "release of a descendants PMC failed: %s", strerror(errno));
381 }
382 
383 /**
384  * @internal
385  * The fork-storm variant: descendants set, then many short-lived children,
386  * so several inherit-and-exit teardowns overlap, then release.
387  */
388 ATF_TC_WITHOUT_HEAD(descendants_fork_storm);
ATF_TC_BODY(descendants_fork_storm,tc)389 ATF_TC_BODY(descendants_fork_storm, tc)
390 {
391 	pmc_id_t id;
392 	int i, status;
393 
394 	require_hwpmc();
395 	id = allocate_soft_pmc_flags(PMC_MODE_TC, PMC_F_DESCENDANTS);
396 	if (id == PMC_ID_INVALID)
397 		atf_tc_skip("no SOFT PMC allocatable with PMC_F_DESCENDANTS");
398 
399 	ATF_REQUIRE_MSG(pmc_attach(id, getpid()) == 0, "pmc_attach: %s",
400 	    strerror(errno));
401 	ATF_REQUIRE_MSG(pmc_start(id) == 0, "pmc_start: %s", strerror(errno));
402 
403 	for (i = 0; i < 32; i++) {
404 		pid_t c = fork();
405 
406 		if (c == 0) {
407 			volatile unsigned long s = 0;
408 			int j;
409 
410 			for (j = 0; j < 200000; j++)
411 				s += j;
412 			_exit(0);
413 		}
414 		if (c > 0)
415 			(void)waitpid(c, &status, 0);
416 	}
417 
418 	ATF_CHECK(pmc_stop(id) == 0);
419 	ATF_CHECK_MSG(pmc_release(id) == 0,
420 	    "release after a descendants fork storm failed: %s",
421 	    strerror(errno));
422 }
423 
ATF_TP_ADD_TCS(tp)424 ATF_TP_ADD_TCS(tp)
425 {
426 
427 	ATF_TP_ADD_TC(tp, attach_detach_bookkeeping);
428 	ATF_TP_ADD_TC(tp, detach_after_target_reaped);
429 	ATF_TP_ADD_TC(tp, release_running_attached);
430 	ATF_TP_ADD_TC(tp, owner_exit_before_target);
431 	ATF_TP_ADD_TC(tp, exhaust_then_release_reverse);
432 	ATF_TP_ADD_TC(tp, descendants_inherit_and_release);
433 	ATF_TP_ADD_TC(tp, descendants_fork_storm);
434 
435 	return (atf_no_error());
436 }
437