From nobody Thu Oct 15 14:29:38 2026 X-Original-To: dev-commits-src-main@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 4j59Sf6f3Zz6w63b for ; Thu, 15 Oct 2026 14:29:38 +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 4j59Sf2pMHz4FSD for ; Thu, 15 Oct 2026 14:29:38 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1792074578; 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=C75EI95SSmlegplnwAN5rPlHUw6xxa3Y74DEiHuO4AY=; b=xLTUge9KqpR7Gt6NuVzoU3qdyL1P36CQDYXe5Q07WSXvmzFZAE8dZ3y22lLmSYj/rLvZaZ BVu2U6q7tKJl/tod8P+khw3nm71+NaU+k2eHTOA+kNGrRMx5An48bwEJWiKCxmn2gEDTcz LVbwDmWd3AlVB0Ri/3rwGg/T/YPvKdk1WhkURgACkwKTgF+rIbfbaeXnP9zHtOM5WTHaC7 Sk3wQUc4AsNkFJ7cvKvzFsjQeVkVlLqiK7DrHANrTe6Jcj6cywTpbm79M8q5w1457ueoeG lkh3nly2CaQDxEt8UZdbv+KEF0jpYuru1ayZQgIxpo+DfbGdKzVboPGneZXKng== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1792074578; b=sFbFirxokWnv5iMXmiTw/G5Xe09H8cXmIoQgkbOFGIc5OxMaHg9Oq5zhg9T4XUes9nWd2J LBnXD+Z50QgE+jJa5zk2iJ34uYhm9CMvdiGsFXPudI7MShujE7BaEnqqPWmUkx26n/y11t TAdkitlwTSZ+ldh5DzCOw4hIU9B4KWjvUEqqYSUkF9qVImWdQt/o961UlKAT+s8Ij2Ugss 2CjBmoSl2xZ5s+Lgp3OmlUOIJYQGA3Fd7KodNS7mmZPMIUVqAgItIHxvLG0xjf/CnsjsGX QIif9WPm5b3fYafCnqO8fdiAl6TUF7rrk4cqpVoV+D/5vg3/Dq9vpqVQ7Kug6w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1792074578; 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=C75EI95SSmlegplnwAN5rPlHUw6xxa3Y74DEiHuO4AY=; b=CKmOM1YNbbCaDBtDYa7zEhHnEPR/vxt21YOKKnW7dOa4RWzuWbX5NqALHzzYIbr5LI9Ivd R7H2CMmLYsfvQdzzqBNgvTRghtly1FgBA847ZsttwWNU3DB+/gsGFPnd3J1Eg9h6KfCgyb Bsy/22PtQMH3kXyXLLqJYn4986hEJRUa9E2DWCqWzKTSG+8CZGzurzLTI0yj60JReS6nrO Ov3WBPOHLxs1PuGB2RDxoq1a8zzzBLIHjuHarUPSztD78ucUubR8WD0Oz7jZk/4No9xKTd IycEzp0B9D6rqddJdgm2Z9ncq4osUac/ILaksn91LBW0Q4COYH0eHzgtPvFu5Q== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4j59Sf1YMRzcdG for ; Thu, 15 Oct 2026 14:29:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3092b by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 15 Oct 2026 14:29:38 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Piotr Kubaj Subject: git: 35a226dded99 - main - tests/sys/kern: stress FP registers across sigreturn on powerpc64 List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository 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: pkubaj X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 35a226dded9934c6beb5945a982a705712454be1 Auto-Submitted: auto-generated Date: Thu, 15 Oct 2026 14:29:38 +0000 Message-Id: <6ad0e352.3092b.551ba6d7@gitrepo.freebsd.org> The branch main has been updated by pkubaj: URL: https://cgit.FreeBSD.org/src/commit/?id=35a226dded9934c6beb5945a982a705712454be1 commit 35a226dded9934c6beb5945a982a705712454be1 Author: Piotr Kubaj AuthorDate: 2026-10-15 14:28:00 +0000 Commit: Piotr Kubaj CommitDate: 2026-10-15 14:28:00 +0000 tests/sys/kern: stress FP registers across sigreturn on powerpc64 This is the program that found the races fixed in 1f1043d550ea. One thread keeps a pattern in f14-f31 across a signal, while helpers pinned to the same CPU keep different patterns in the real registers and get woken up from another CPU to preempt it at random points. If the kernel ever hands it somebody else's registers, the pattern doesn't match. The register loads, the thr_kill() and the handshake with the helper are in assembly so that nothing touches the FP registers between sigreturn and the check. It runs for ten seconds and is skipped with fewer than two CPUs. Requested by: adrian Approved by: jhibbits MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D60411 --- tests/sys/kern/Makefile | 6 + tests/sys/kern/sigreturn_fpregs.c | 233 ++++++++++++++++++++++++++++++++ tests/sys/kern/sigreturn_fpregs_ppc64.S | 175 ++++++++++++++++++++++++ 3 files changed, 414 insertions(+) diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile index c193e7108dc0..a59168094c58 100644 --- a/tests/sys/kern/Makefile +++ b/tests/sys/kern/Makefile @@ -45,6 +45,12 @@ ATF_TESTS_C+= resolve_beneath_test ATF_TESTS_C+= sched_affinity ATF_TESTS_C+= shutdown_dgram ATF_TESTS_C+= sigaltstack +.if ${MACHINE_ARCH:Mpowerpc64*} != "" +ATF_TESTS_C+= sigreturn_fpregs +SRCS.sigreturn_fpregs= sigreturn_fpregs.c sigreturn_fpregs_ppc64.S +LIBADD.sigreturn_fpregs+= pthread +TEST_METADATA.sigreturn_fpregs+= is_exclusive="true" +.endif ATF_TESTS_C+= sigwait ATF_TESTS_C+= socket_accept ATF_TESTS_C+= socket_accf diff --git a/tests/sys/kern/sigreturn_fpregs.c b/tests/sys/kern/sigreturn_fpregs.c new file mode 100644 index 000000000000..e99f60b154d3 --- /dev/null +++ b/tests/sys/kern/sigreturn_fpregs.c @@ -0,0 +1,233 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Piotr Kubaj + */ + +/* + * Stress test for the lazy reload of the floating point registers after + * sigreturn(2) on powerpc64. + * + * sigreturn turns the FPU off for the thread; the next FP instruction takes + * the FP unavailable trap, which reloads the registers from the PCB. The + * checker loads f14-f31 with a pattern, sends itself a signal and stores the + * registers again, so that store takes the trap. Between the two it pings a + * helper thread pinned to its own CPU, so the hardware registers hold the + * helper's values when the reload runs; more helpers on the same CPU are + * woken from another CPU so that ULE preempts the checker at random points + * of the sequence (only a remote wakeup preempts immediately). Any mismatch + * means the kernel handed the checker another thread's registers. Before + * 1f1043d550ea ("powerpc: keep FP, VMX and VSX ownership changes atomic + * with preemption") a POWER9 saw about one mismatch per 3000 round trips. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* sigreturn_fpregs_ppc64.S */ +void fp_roundtrip(const double *in, double *out, long tid, long sig, + long wfd, long rfd); +void fp_load_all(const double *pat); + +#define DURATION 10 /* seconds */ +#define NPOLL 4 /* 3 woken by the waker, #3 woken by the checker */ +#define NREGS 18 /* f14-f31 */ + +static double pollpat[NPOLL][32]; +static int pin_in[NPOLL][2], pin_out[NPOLL][2]; +static int checker_cpu, waker_cpu; + +static void +xfer(int fd, int wr, char *c) +{ + ssize_t n; + + do + n = wr ? write(fd, c, 1) : read(fd, c, 1); + while (n < 0 && errno == EINTR); + if (n != 1) + err(1, wr ? "write" : "read"); +} + +static void +pin_thread(int cpu) +{ + cpuset_t cs; + + CPU_ZERO(&cs); + CPU_SET(cpu, &cs); + if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cs), + &cs) != 0) + err(1, "cpuset_setaffinity(%d)", cpu); +} + +static void +handler(int sig __unused) +{ +} + +static void * +polluter(void *arg) +{ + int i = (int)(intptr_t)arg; + char c; + + pin_thread(checker_cpu); + if (i == NPOLL - 1) /* the checker's helper: distinctive FPSCR */ + __asm__ volatile("mtfsb1 31"); + for (;;) { + xfer(pin_in[i][0], 0, &c); + fp_load_all(pollpat[i]); + xfer(pin_out[i][1], 1, &c); + } + return (NULL); +} + +static void * +waker(void *arg __unused) +{ + uint64_t until; + char c = 'x'; + int i; + + pin_thread(waker_cpu); + for (;;) { + for (i = 0; i < NPOLL - 1; i++) { + /* + * Random 0-20 us delay so the preemption samples the + * checker's loop at uniformly random phases. + */ + until = __builtin_ppc_get_timebase() + random() % 10240; + while (__builtin_ppc_get_timebase() < until) + ; + xfer(pin_in[i][1], 1, &c); + xfer(pin_out[i][0], 0, &c); + } + } + return (NULL); +} + +/* Pick two distinct CPUs this process may run on. */ +static int +pick_cpus(void) +{ + cpuset_t cs; + int i, n; + + CPU_ZERO(&cs); + ATF_REQUIRE_EQ(0, cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, + -1, sizeof(cs), &cs)); + n = 0; + for (i = 0; i < CPU_SETSIZE; i++) { + if (!CPU_ISSET(i, &cs)) + continue; + if (n == 0) + checker_cpu = i; + else if (n == 1) + waker_cpu = i; + n++; + } + return (n); +} + +ATF_TC(sigreturn_fpregs); +ATF_TC_HEAD(sigreturn_fpregs, tc) +{ + atf_tc_set_md_var(tc, "descr", "FP registers reloaded after sigreturn " + "are the thread's own under preemption"); + atf_tc_set_md_var(tc, "timeout", "60"); +} +ATF_TC_BODY(sigreturn_fpregs, tc) +{ + double in[NREGS], out[NREGS + 1]; + struct sigaction sa; + sigset_t set; + pthread_t pt; + uint64_t iters, bad, v, first_exp, first_got; + time_t end; + long tid; + int first_reg, k, p, r; + + if (pick_cpus() < 2) + atf_tc_skip("needs two CPUs"); + pin_thread(checker_cpu); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + ATF_REQUIRE_EQ(0, sigaction(SIGUSR1, &sa, NULL)); + + /* The helpers never see the signal. */ + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + ATF_REQUIRE_EQ(0, pthread_sigmask(SIG_BLOCK, &set, NULL)); + for (p = 0; p < NPOLL; p++) { + for (r = 0; r < 32; r++) { + v = 0xbad0000000000000ULL | ((uint64_t)(p + 1) << 32) | + r; + memcpy(&pollpat[p][r], &v, sizeof(v)); + } + ATF_REQUIRE_EQ(0, pipe(pin_in[p])); + ATF_REQUIRE_EQ(0, pipe(pin_out[p])); + ATF_REQUIRE_EQ(0, pthread_create(&pt, NULL, polluter, + (void *)(intptr_t)p)); + } + ATF_REQUIRE_EQ(0, pthread_create(&pt, NULL, waker, NULL)); + ATF_REQUIRE_EQ(0, pthread_sigmask(SIG_UNBLOCK, &set, NULL)); + + for (r = 0; r < NREGS; r++) { + v = 0xc0de000000000000ULL | (uint64_t)(r + 14); + memcpy(&in[r], &v, sizeof(v)); + } + thr_self(&tid); + + iters = bad = 0; + first_reg = -1; + first_exp = first_got = 0; + end = time(NULL) + DURATION; + while (time(NULL) < end) { + for (k = 0; k < 2000; k++) { + memset(out, 0, sizeof(out)); + fp_roundtrip(in, out, tid, SIGUSR1, pin_in[NPOLL - 1][1], + pin_out[NPOLL - 1][0]); + iters++; + if (memcmp(in, out, sizeof(in)) == 0) + continue; + bad++; + if (first_reg >= 0) + continue; + for (r = 0; r < NREGS; r++) { + memcpy(&first_exp, &in[r], sizeof(first_exp)); + memcpy(&first_got, &out[r], sizeof(first_got)); + if (first_exp != first_got) { + first_reg = r + 14; + break; + } + } + } + } + if (bad != 0) + atf_tc_fail("%ju of %ju sigreturn round trips came back with " + "another thread's FP registers (first: f%d expected %#jx " + "got %#jx)", (uintmax_t)bad, (uintmax_t)iters, first_reg, + (uintmax_t)first_exp, (uintmax_t)first_got); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, sigreturn_fpregs); + + return (atf_no_error()); +} diff --git a/tests/sys/kern/sigreturn_fpregs_ppc64.S b/tests/sys/kern/sigreturn_fpregs_ppc64.S new file mode 100644 index 000000000000..f9942c3e0e5b --- /dev/null +++ b/tests/sys/kern/sigreturn_fpregs_ppc64.S @@ -0,0 +1,175 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Piotr Kubaj + */ + +/* + * Helpers for sigreturn_fpregs.c, kept in assembly so that nothing touches + * the FP registers between the sigreturn and the stores that take the lazy + * reload trap. + * + * fp_roundtrip(in, out, tid, sig, wfd, rfd): load f14-f31 from in, raise + * sig on tid with thr_kill(2), write a byte to wfd and read one from rfd + * (both raw system calls: they let the helper on this CPU run), then store + * f14-f31 and the FPSCR to out. + * + * fp_load_all(pat): load f0-f31 from pat. + */ + + .abiversion 2 + .text +/* void fp_roundtrip(const double *in, double *out, long tid, long sig, long wfd, long rfd) */ + .globl fp_roundtrip + .type fp_roundtrip,@function +fp_roundtrip: + mflr 0 + std 0,16(1) + stdu 1,-320(1) + stfd 14,32(1) + stfd 15,40(1) + stfd 16,48(1) + stfd 17,56(1) + stfd 18,64(1) + stfd 19,72(1) + stfd 20,80(1) + stfd 21,88(1) + stfd 22,96(1) + stfd 23,104(1) + stfd 24,112(1) + stfd 25,120(1) + stfd 26,128(1) + stfd 27,136(1) + stfd 28,144(1) + stfd 29,152(1) + stfd 30,160(1) + stfd 31,168(1) + std 28,192(1) + std 29,200(1) + std 30,176(1) + std 31,184(1) + mr 30,3 + mr 31,4 + mr 28,7 + mr 29,8 + lfd 14,0(30) + lfd 15,8(30) + lfd 16,16(30) + lfd 17,24(30) + lfd 18,32(30) + lfd 19,40(30) + lfd 20,48(30) + lfd 21,56(30) + lfd 22,64(30) + lfd 23,72(30) + lfd 24,80(30) + lfd 25,88(30) + lfd 26,96(30) + lfd 27,104(30) + lfd 28,112(30) + lfd 29,120(30) + lfd 30,128(30) + lfd 31,136(30) + li 0,433 /* SYS_thr_kill */ + mr 3,5 + mr 4,6 + sc + /* ping the helper on this CPU: it leaves ITS registers in the FPU */ + li 0,4 /* SYS_write(wfd, buf, 1) */ + mr 3,28 + addi 4,1,208 + li 5,1 + sc + li 0,3 /* SYS_read(rfd, buf, 1) */ + mr 3,29 + addi 4,1,208 + li 5,1 + sc + /* first FP instruction since sigreturn: lazy reload happens here */ + stfd 14,0(31) + stfd 15,8(31) + stfd 16,16(31) + stfd 17,24(31) + stfd 18,32(31) + stfd 19,40(31) + stfd 20,48(31) + stfd 21,56(31) + stfd 22,64(31) + stfd 23,72(31) + stfd 24,80(31) + stfd 25,88(31) + stfd 26,96(31) + stfd 27,104(31) + stfd 28,112(31) + stfd 29,120(31) + stfd 30,128(31) + stfd 31,136(31) + mffs 0 /* FPSCR, for diagnosis */ + stfd 0,144(31) + lfd 14,32(1) + lfd 15,40(1) + lfd 16,48(1) + lfd 17,56(1) + lfd 18,64(1) + lfd 19,72(1) + lfd 20,80(1) + lfd 21,88(1) + lfd 22,96(1) + lfd 23,104(1) + lfd 24,112(1) + lfd 25,120(1) + lfd 26,128(1) + lfd 27,136(1) + lfd 28,144(1) + lfd 29,152(1) + lfd 30,160(1) + lfd 31,168(1) + ld 28,192(1) + ld 29,200(1) + ld 30,176(1) + ld 31,184(1) + addi 1,1,320 + ld 0,16(1) + mtlr 0 + blr + .size fp_roundtrip,.-fp_roundtrip + +/* void fp_load_all(const double *pat): leave pat[0..31] live in f0-f31 */ + .globl fp_load_all + .type fp_load_all,@function +fp_load_all: + lfd 0,0(3) + lfd 1,8(3) + lfd 2,16(3) + lfd 3,24(3) + lfd 4,32(3) + lfd 5,40(3) + lfd 6,48(3) + lfd 7,56(3) + lfd 8,64(3) + lfd 9,72(3) + lfd 10,80(3) + lfd 11,88(3) + lfd 12,96(3) + lfd 13,104(3) + lfd 14,112(3) + lfd 15,120(3) + lfd 16,128(3) + lfd 17,136(3) + lfd 18,144(3) + lfd 19,152(3) + lfd 20,160(3) + lfd 21,168(3) + lfd 22,176(3) + lfd 23,184(3) + lfd 24,192(3) + lfd 25,200(3) + lfd 26,208(3) + lfd 27,216(3) + lfd 28,224(3) + lfd 29,232(3) + lfd 30,240(3) + lfd 31,248(3) + blr + .size fp_load_all,.-fp_load_all + .section .note.GNU-stack,"",@progbits