git: ccb59683b983 - main - arm64: add tests for swp/swpb emulation
Date: Mon, 15 May 2023 15:42:24 UTC
The branch main has been updated by kevans:
URL: https://cgit.FreeBSD.org/src/commit/?id=ccb59683b98360afaf5b5bb641a68fea22c68d0b
commit ccb59683b98360afaf5b5bb641a68fea22c68d0b
Author: Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2023-05-15 15:42:16 +0000
Commit: Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2023-05-15 15:42:16 +0000
arm64: add tests for swp/swpb emulation
One test is suitable to be hooked up to the build, so I've done this
here. The other test lives in tools/regression because failure is a
bit more subjective -- generally, one runs it for some unbounded amount
of time and observe if it eventually exits because two threads acquired
the same mutex.
Reviewed by: imp, mmel
Sponsored by: Stormshield
Sponsored by: Klara, Inc.
Differential Revision: https://reviews.freebsd.org/D39668
---
etc/mtree/BSD.tests.dist | 2 +
tests/sys/Makefile | 1 +
tests/sys/compat32/Makefile | 6 +
tests/sys/compat32/Makefile.inc | 4 +
tests/sys/compat32/aarch64/Makefile | 24 ++
tests/sys/compat32/aarch64/common.sh | 9 +
tests/sys/compat32/aarch64/swp_cond_test.sh | 14 +
tests/sys/compat32/aarch64/swp_cond_test_impl.S | 413 ++++++++++++++++++++++
tests/sys/compat32/aarch64/swp_test.sh | 14 +
tests/sys/compat32/aarch64/swp_test_impl.S | 216 +++++++++++
tools/regression/compat32/aarch64/Makefile | 4 +
tools/regression/compat32/aarch64/swp_test_impl.S | 410 +++++++++++++++++++++
12 files changed, 1117 insertions(+)
diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 5d99653100df..77c07960f938 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -750,6 +750,8 @@
..
..
..
+ compat32
+ ..
devrandom
..
dtrace
diff --git a/tests/sys/Makefile b/tests/sys/Makefile
index 8f83445f9d91..3e9a30105a1d 100644
--- a/tests/sys/Makefile
+++ b/tests/sys/Makefile
@@ -10,6 +10,7 @@ TESTS_SUBDIRS+= ${_audit}
TESTS_SUBDIRS+= auditpipe
TESTS_SUBDIRS+= capsicum
TESTS_SUBDIRS+= ${_cddl}
+TESTS_SUBDIRS+= compat32
TESTS_SUBDIRS+= devrandom
TESTS_SUBDIRS+= fifo
TESTS_SUBDIRS+= file
diff --git a/tests/sys/compat32/Makefile b/tests/sys/compat32/Makefile
new file mode 100644
index 000000000000..31834de16246
--- /dev/null
+++ b/tests/sys/compat32/Makefile
@@ -0,0 +1,6 @@
+
+.if exists(${.CURDIR}/${MACHINE_ARCH})
+SUBDIR+= ${MACHINE_ARCH}
+.endif
+
+.include <bsd.subdir.mk>
diff --git a/tests/sys/compat32/Makefile.inc b/tests/sys/compat32/Makefile.inc
new file mode 100644
index 000000000000..0220ac0431e9
--- /dev/null
+++ b/tests/sys/compat32/Makefile.inc
@@ -0,0 +1,4 @@
+
+TESTSDIR= ${TESTSBASE}/sys/compat32
+
+.include "../Makefile.inc"
diff --git a/tests/sys/compat32/aarch64/Makefile b/tests/sys/compat32/aarch64/Makefile
new file mode 100644
index 000000000000..716182b15d9c
--- /dev/null
+++ b/tests/sys/compat32/aarch64/Makefile
@@ -0,0 +1,24 @@
+PACKAGE= tests
+FILESGROUPS+= asmprogs
+
+ACFLAGS= -target armv7-unknown-freebsd${OS_REVISION} -nostdlib -Wl,-e -Wl,main -static
+
+TAP_TESTS_SH+= swp_cond_test
+TAP_TESTS_SH+= swp_test
+${PACKAGE}FILES+= common.sh
+
+# Each test will individually respect the compat.arm.emul_swp
+# sysctl upon entry.
+TEST_METADATA.swp_cond_test+= is_exclusive=true
+TEST_METADATA.swp_test+= is_exclusive=true
+
+asmprogsMODE= 0755
+asmprogs+= swp_cond_test_impl swp_test_impl
+asmprogsDIR= ${TESTSDIR}
+
+.for aprog in ${asmprogs}
+${aprog}: ${aprog}.S
+ ${CC} ${ACFLAGS} -o ${.TARGET} ${.ALLSRC}
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/compat32/aarch64/common.sh b/tests/sys/compat32/aarch64/common.sh
new file mode 100644
index 000000000000..f8b02e8ac157
--- /dev/null
+++ b/tests/sys/compat32/aarch64/common.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if ! sysctl -n kern.features.compat_freebsd_32bit >/dev/null 2>&1; then
+ echo "1..0 # Skipped: Kernel not built with COMPAT_FREEBSD32"
+ exit 0
+elif ! sysctl -n kern.supported_archs | grep -q '\<armv7\>'; then
+ echo "1..0 # Skipped: 32-bit ARM not supported on this hardware"
+ exit 0
+fi
diff --git a/tests/sys/compat32/aarch64/swp_cond_test.sh b/tests/sys/compat32/aarch64/swp_cond_test.sh
new file mode 100644
index 000000000000..8edfa43b3e7e
--- /dev/null
+++ b/tests/sys/compat32/aarch64/swp_cond_test.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+scriptdir=$(dirname $(realpath "$0"))
+
+. ${scriptdir}/common.sh
+
+# Ensure emul_swp is enabled just for this test; we'll turn it back off if
+# it wasn't enabled before the test.
+emul_swpval=$(sysctl -n compat.arm.emul_swp)
+sysctl compat.arm.emul_swp=1 >/dev/null
+${scriptdir}/swp_test_impl
+if [ "$emul_swpval" -ne 1 ]; then
+ sysctl compat.arm.emul_swp="$emul_swpval" >/dev/null
+fi
diff --git a/tests/sys/compat32/aarch64/swp_cond_test_impl.S b/tests/sys/compat32/aarch64/swp_cond_test_impl.S
new file mode 100644
index 000000000000..d29db44832b1
--- /dev/null
+++ b/tests/sys/compat32/aarch64/swp_cond_test_impl.S
@@ -0,0 +1,413 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Warner Losh
+ * Copyright (c) 2023 Stormshield
+ * Copyright (c) 2023 Klara, Inc.
+ */
+
+#include <sys/syscall.h>
+
+#define STDOUT_FILENO 1
+#define SWP_MAGIC 0xffc0
+#define SWPB_MAGIC 0xc0c0
+
+ .text
+ .file "swp_test.S"
+ .syntax unified
+ .globl main
+ .p2align 2
+ .type main,%function
+ .code 32
+
+main:
+ sub sp, #0x04
+ /* r4 is our failed test counter */
+ mov r4, #0
+ /* r6 is our current teset counter */
+ mov r6, #1
+
+ movw r0, :lower16:.L.testheader
+ movt r0, :upper16:.L.testheader
+ ldr r1, =(.L.testheaderEnd - .L.testheader - 1)
+ bl print
+
+ /* eq */
+ bl reset
+ mov r1, #SWP_MAGIC
+ cmp r1, r1
+ swpeq r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 1f
+
+ /* !eq */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0
+ cmp r1, r2
+ swpeq r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+1:
+ movw r0, :lower16:.L.eq
+ movt r0, :upper16:.L.eq
+ ldr r1, =(.L.eqEnd - .L.eq - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* cs */
+ bl reset
+ mov r1, #SWP_MAGIC
+ movw r3, #0xffff
+ movt r3, #0xffff
+ /* Overflow */
+ adds r2, r3, r3
+ swpcs r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 2f
+
+ /* !cs */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r3, #0x00
+ adds r2, r3, #0x08
+ swpcs r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+2:
+ movw r0, :lower16:.L.cs
+ movt r0, :upper16:.L.cs
+ ldr r1, =(.L.csEnd - .L.cs - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* mi */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0
+ /* Underflow */
+ subs r2, r2, #0x05
+ swpmi r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 3f
+
+ /* !mi */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x10
+ subs r2, r2, #0x08
+ swpmi r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+3:
+ movw r0, :lower16:.L.mi
+ movt r0, :upper16:.L.mi
+ ldr r1, =(.L.miEnd - .L.mi - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* vs */
+ bl reset
+ mov r1, #SWP_MAGIC
+ movw r3, #0xffff
+ movt r3, #0x7fff
+ /* Overflow */
+ adds r2, r3, #0x10
+ swpvs r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 4f
+
+ /* !vs */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r3, #0x00
+ adds r2, r3, #0x08
+ swpvs r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+4:
+ movw r0, :lower16:.L.vs
+ movt r0, :upper16:.L.vs
+ ldr r1, =(.L.vsEnd - .L.vs - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* hi */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x00
+ mov r3, #0x01
+ cmp r3, r2
+ swphi r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 5f
+
+ /* !hi */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x00
+ mov r3, #0x01
+ cmp r2, r3
+ swphi r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+5:
+ movw r0, :lower16:.L.hi
+ movt r0, :upper16:.L.hi
+ ldr r1, =(.L.hiEnd - .L.hi - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* ge */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x01
+ cmp r2, r2
+ swpge r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 6f
+
+ /* !ge */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x00
+ mov r3, #0x01
+ cmp r2, r3
+ swpge r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+6:
+ movw r0, :lower16:.L.ge
+ movt r0, :upper16:.L.ge
+ ldr r1, =(.L.geEnd - .L.ge - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ /* gt */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x00
+ mov r3, #0x01
+ cmp r3, r2
+ swpgt r0, r1, [r0]
+ bl expect_success
+
+ /* Returned 0 (bad) or 1 (ok) */
+ cmp r0, #0
+ beq 7f
+
+ /* !ge */
+ bl reset
+ mov r1, #SWP_MAGIC
+ mov r2, #0x00
+ mov r3, #0x01
+ cmp r2, r3
+ swpgt r0, r1, [r0]
+ bl expect_fail
+
+ /* Don't care about the return of the second one, just print */
+7:
+ movw r0, :lower16:.L.gt
+ movt r0, :upper16:.L.gt
+ ldr r1, =(.L.gtEnd - .L.gt - 1)
+ bl print_result
+ add r6, r6, #1 /* Next test */
+
+ mov r0, r4 /* retval */
+ ldr r7, =SYS_exit
+ swi 0
+
+ .p2align 2
+ .type print_result,%function
+ .code 32
+print_result:
+ push {r4, r5, lr}
+ /* Save the label, size for our result */
+ mov r4, r0
+ mov r5, r1
+
+ movw r0, :lower16:.L.ok
+ movt r0, :upper16:.L.ok
+ ldr r1, =(.L.okEnd - .L.ok - 1)
+ bl print
+ mov r0, r6
+ add r0, #0x30 /* "0" + test number */
+ mov r1, #0x01
+ str r0, [sp]
+ mov r0, sp
+ bl print
+ movw r0, :lower16:.L.swp
+ movt r0, :upper16:.L.swp
+ ldr r1, =(.L.swpEnd - .L.swp - 1)
+ bl print
+ mov r0, r4
+ mov r1, r5
+ bl print
+ movw r0, :lower16:.L.term
+ movt r0, :upper16:.L.term
+ ldr r1, =(.L.termEnd - .L.term - 1)
+ bl print
+
+ pop {r4, r5, lr}
+ bx lr
+
+ .p2align 2
+ .type reset,%function
+ .code 32
+reset:
+ /* Reset sp[0] and return the address used */
+ mov r0, #0x03
+ str r0, [sp]
+ mov r0, sp
+ bx lr
+
+ .p2align 2
+ .type expect_fail,%function
+ .code 32
+expect_fail:
+ /* Just check the stack value */
+ ldr r0, [sp]
+ mov r1, #0x03
+ cmp r0, r1
+ bne 1f
+
+ /* Success (not swapped) */
+ mov r0, #1
+ bx lr
+
+1:
+ /* Fail (swapped) */
+ /* Print the "not" part */
+ movw r0, :lower16:.L.not
+ movt r0, :upper16:.L.not
+ ldr r1, =(.L.notEnd - .L.not - 1)
+ push {lr}
+ bl print
+ pop {lr}
+
+ /* Failed */
+ add r4, r4, #1
+ mov r0, #0
+ bx lr
+
+ .p2align 2
+ .type expect_success,%function
+ .code 32
+expect_success:
+ /* Old value should be 3 */
+ cmp r0, #0x03
+ beq 1f
+ b 3f
+
+1:
+ /* Check stack value */
+ ldr r0, [sp]
+ mov r1, #SWP_MAGIC
+ cmp r0, r1
+ beq 2f
+ b 3f
+
+2:
+ mov r0, #1
+ bx lr
+
+3:
+ /* Print the "not" part */
+ movw r0, :lower16:.L.not
+ movt r0, :upper16:.L.not
+ ldr r1, =(.L.notEnd - .L.not - 1)
+ push {lr}
+ bl print
+ pop {lr}
+
+ /* Failed */
+ add r4, r4, #1
+ mov r0, #0
+ bx lr
+
+ .p2align 2
+ .type print,%function
+ .code 32
+print:
+ /* r0 - string, r1 = size */
+ mov r2, r1
+ mov r1, r0
+ ldr r0, =STDOUT_FILENO
+ ldr r7, =SYS_write
+ swi 0
+
+ bx lr
+
+.L.testheader:
+ .asciz "1..7\n"
+.L.testheaderEnd:
+ .size .L.testheader, .L.testheaderEnd - .L.testheader
+
+.L.not:
+ .asciz "not "
+.L.notEnd:
+ .size .L.not, .L.notEnd - .L.not
+.L.ok:
+ .asciz "ok "
+.L.okEnd:
+ .size .L.ok, .L.okEnd - .L.ok
+.L.swp:
+ .asciz " - swp"
+.L.swpEnd:
+ .size .L.swp, .L.swpEnd - .L.swp
+.L.eq:
+ .asciz "eq"
+.L.eqEnd:
+ .size .L.eq, .L.eqEnd - .L.eq
+.L.cs:
+ .asciz "cs"
+.L.csEnd:
+ .size .L.cs, .L.csEnd - .L.cs
+.L.mi:
+ .asciz "mi"
+.L.miEnd:
+ .size .L.mi, .L.miEnd - .L.mi
+.L.vs:
+ .asciz "vs"
+.L.vsEnd:
+ .size .L.vs, .L.vsEnd - .L.vs
+.L.hi:
+ .asciz "hi"
+.L.hiEnd:
+ .size .L.hi, .L.hiEnd - .L.hi
+.L.ge:
+ .asciz "ge"
+.L.geEnd:
+ .size .L.ge, .L.geEnd - .L.ge
+.L.gt:
+ .asciz "gt"
+.L.gtEnd:
+ .size .L.gt, .L.gtEnd - .L.gt
+.L.term:
+ .asciz "\n"
+.L.termEnd:
+ .size .L.term, .L.termEnd - .L.term
diff --git a/tests/sys/compat32/aarch64/swp_test.sh b/tests/sys/compat32/aarch64/swp_test.sh
new file mode 100644
index 000000000000..8edfa43b3e7e
--- /dev/null
+++ b/tests/sys/compat32/aarch64/swp_test.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+scriptdir=$(dirname $(realpath "$0"))
+
+. ${scriptdir}/common.sh
+
+# Ensure emul_swp is enabled just for this test; we'll turn it back off if
+# it wasn't enabled before the test.
+emul_swpval=$(sysctl -n compat.arm.emul_swp)
+sysctl compat.arm.emul_swp=1 >/dev/null
+${scriptdir}/swp_test_impl
+if [ "$emul_swpval" -ne 1 ]; then
+ sysctl compat.arm.emul_swp="$emul_swpval" >/dev/null
+fi
diff --git a/tests/sys/compat32/aarch64/swp_test_impl.S b/tests/sys/compat32/aarch64/swp_test_impl.S
new file mode 100644
index 000000000000..0e8047f1a6cf
--- /dev/null
+++ b/tests/sys/compat32/aarch64/swp_test_impl.S
@@ -0,0 +1,216 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Warner Losh
+ * Copyright (c) 2023 Stormshield
+ * Copyright (c) 2023 Klara, Inc.
+ */
+
+#include <sys/syscall.h>
+
+#define STDOUT_FILENO 1
+#define SWP_MAGIC 0xffc0
+#define SWPB_MAGIC 0xc0c0
+
+ .text
+ .file "swp_test.S"
+ .syntax unified
+ .globl main
+ .p2align 2
+ .type main,%function
+ .code 32
+
+main:
+ sub sp, #0x04
+ /* r4 is our failed test counter */
+ mov r4, #0
+
+ movw r0, :lower16:.L.testheader
+ movt r0, :upper16:.L.testheader
+ ldr r1, =(.L.testheaderEnd - .L.testheader - 1)
+ bl print
+
+ /* Target address */
+ mov r0, #0x03
+ str r0, [sp]
+ mov r0, sp
+
+ /* Load value */
+ mov r1, #SWP_MAGIC
+
+ /* swp it */
+ swp r0, r1, [r0]
+
+ /* Old value should be 3 */
+ cmp r0, #0x03
+ bne 1f
+
+ /* Check stack value */
+ ldr r0, [sp]
+ mov r1, #SWP_MAGIC
+ cmp r0, r1
+ bne 1f
+ b 2f
+
+1:
+ /* Denote the failed test */
+ add r4, #1
+ /* "No" part of the notification */
+ movw r0, :lower16:.L.boknot
+ movt r0, :upper16:.L.boknot
+ ldr r1, =(.L.boknotEnd - .L.boknot - 1)
+ bl print
+
+2:
+ /* Notify */
+ movw r0, :lower16:.L.ok1
+ movt r0, :upper16:.L.ok1
+ ldr r1, =(.L.ok1End - .L.ok1 - 1)
+ bl print
+
+ movw r5, #SWPB_MAGIC
+ movt r5, #SWPB_MAGIC
+
+ /* Using r6 as our accumulator */
+ mov r6, sp
+ /* Simplify the loop */
+ sub r6, #1
+3:
+ /* Restore our magic value every time */
+ str r5, [sp]
+ /* Move on to the next byte */
+ add r6, #1
+
+ /* swp it in */
+ mov r0, r6
+ mov r1, #3
+ swpb r0, r1, [r0]
+
+ /* Check the old value */
+ cmp r0, #0xc0
+ bne 6f
+
+ /* Check the stack value */
+ ldrb r0, [r6]
+ cmp r0, #0x03
+ bne 6f
+
+ /* Just loop over the rest of the word and check those values. */
+ mov r1, r6
+ sub r1, sp
+
+ mov r0, #0x00
+4:
+ cmp r0, r1
+ beq 5f
+
+ /* Check the next byte */
+ ldrb r3, [sp, r0]
+ cmp r3, #0xc0
+ bne 6f
+
+5:
+ add r0, #0x01
+ cmp r0, #0x04
+ /* Hit the end, this one succeeded */
+ beq 7f
+
+ /* Move on to the next byte */
+ b 4b
+
+6:
+ /* Denote the failed test */
+ add r4, #1
+ /* "No" part of the notification */
+ movw r0, :lower16:.L.boknot
+ movt r0, :upper16:.L.boknot
+ ldr r1, =(.L.boknotEnd - .L.boknot - 1)
+ bl print
+
+ /* FALLTHROUGH */
+7:
+ /* "ok" part */
+ movw r0, :lower16:.L.bok
+ movt r0, :upper16:.L.bok
+ ldr r1, =(.L.bokEnd - .L.bok - 1)
+ bl print
+
+ /* test number */
+ mov r0, r6
+ sub r0, sp
+ add r0, #0x32 /* "0" + 2 because we start at test 2. */
+ mov r1, #0x01
+ str r0, [sp]
+ mov r0, sp
+ bl print
+
+ /* boklabel */
+ movw r0, :lower16:.L.boklabel
+ movt r0, :upper16:.L.boklabel
+ ldr r1, =(.L.boklabelEnd - .L.boklabel - 1)
+ bl print
+
+ /* index */
+ mov r0, r6
+ sub r0, sp
+ add r0, #0x30 /* "0" */
+ str r0, [sp]
+ mov r0, sp
+ mov r1, #0x01
+ bl print
+
+ /* bokterm */
+ movw r0, :lower16:.L.bokterm
+ movt r0, :upper16:.L.bokterm
+ ldr r1, =(.L.boktermEnd - .L.bokterm - 1)
+ bl print
+
+ mov r0, sp
+ add r0, #0x3
+ cmp r0, r6
+ bne 3b
+
+ mov r0, r4 /* retval */
+ ldr r7, =SYS_exit
+ swi 0
+
+ .p2align 2
+ .type print,%function
+ .code 32
+print:
+ /* r0 - string, r1 = size */
+ mov r2, r1
+ mov r1, r0
+ ldr r0, =STDOUT_FILENO
+ ldr r7, =SYS_write
+ swi 0
+
+ bx lr
+
+.L.testheader:
+ .asciz "1..5\n"
+.L.testheaderEnd:
+ .size .L.testheader, .L.testheaderEnd - .L.testheader
+
+ /* Maybe not the most efficient, but meh. */
+.L.ok1:
+ .asciz "ok 1 - swp\n"
+.L.ok1End:
+ .size .L.ok1, .L.ok1End - .L.ok1
+
+.L.boknot:
+ .asciz "not "
+.L.boknotEnd:
+ .size .L.boknot, .L.boknotEnd - .L.boknot
+.L.bok:
+ .asciz "ok "
+.L.bokEnd:
+ .size .L.bok, .L.bokEnd - .L.bok
+.L.boklabel:
+ .asciz " - swpb["
+.L.boklabelEnd:
+ .size .L.boklabel, .L.boklabelEnd - .L.boklabel
+.L.bokterm:
+ .asciz "]\n"
+.L.boktermEnd:
+ .size .L.bokterm, .L.boktermEnd - .L.bokterm
diff --git a/tools/regression/compat32/aarch64/Makefile b/tools/regression/compat32/aarch64/Makefile
new file mode 100644
index 000000000000..34428a58caa1
--- /dev/null
+++ b/tools/regression/compat32/aarch64/Makefile
@@ -0,0 +1,4 @@
+ACFLAGS= -target armv7-unknown-freebsd${OS_REVISION} -nostdlib -Wl,-e -Wl,main -static -mhwdiv=arm
+
+swp_test_impl: swp_test_impl.S
+ ${CC} ${ACFLAGS} -o ${.TARGET} ${.ALLSRC}
diff --git a/tools/regression/compat32/aarch64/swp_test_impl.S b/tools/regression/compat32/aarch64/swp_test_impl.S
new file mode 100644
index 000000000000..9f28ab17748e
--- /dev/null
+++ b/tools/regression/compat32/aarch64/swp_test_impl.S
@@ -0,0 +1,410 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2021 Warner Losh
+ * Copyright (c) 2023 Stormshield
+ * Copyright (c) 2023 Klara, Inc.
+ */
+
+#include <sys/syscall.h>
+
+#define STDOUT_FILENO 1
+
+#define MUTEX_LOCKED 0x01
+#define MUTEX_UNLOCKED 0x00
+
+#define STACK_SIZE 4096
+#define TLS_SIZE 4096
+
+ .text
+ .file "swp_test.S"
+ .syntax unified
+ .globl main
+ .p2align 2
+ .type main,%function
+ .code 32
+
+main:
+ /*
+ * Stack slots:
+ * 0 - Sync word
+ * 1 - Thread id
+ * 2 - Shared word
+ */
+ sub sp, sp, #12
+
+ /* Print a message */
+ movw r0, :lower16:.L.mainmsg
+ movt r0, :upper16:.L.mainmsg
+ ldr r1, =(.L.mainmsgEnd - .L.mainmsg - 1)
+ bl print
+
+ /* Create two secondary threads */
+ mov r0, #1
+ str r0, [sp, #4] /* Thread ID */
+ movw r0, :lower16:secondary_thread
+ movt r0, :upper16:secondary_thread
+ mov r1, sp
+ movw r2, :lower16:stack1
+ movt r2, :upper16:stack1
+ movw r3, :lower16:tls1
+ movt r3, :upper16:tls1
+ bl create_thr
+
+1:
+ /*
+ * Wait for the first new thread to ack its existence by
+ * incrementing the thread id.
+ */
+ ldr r0, [sp, #4]
+ cmp r0, #1
+ bne 2f
+ ldr r7, =SYS_sched_yield
+ swi 0
+ b 1b
+
+2:
+ /* Create thread #2 */
+ movw r0, :lower16:secondary_thread
+ movt r0, :upper16:secondary_thread
+ mov r1, sp
+ movw r2, :lower16:stack2
+ movt r2, :upper16:stack2
+ movw r3, :lower16:tls2
+ movt r3, :upper16:tls2
+ bl create_thr
+
+3:
+ /*
+ * Wait for the first new thread to ack its existence by
+ * incrementing the thread id.
+ */
+ ldr r0, [sp, #4]
+ cmp r0, #2
+ bne 4f
+ ldr r7, =SYS_sched_yield
+ swi 0
+ b 3b
+
+ /* Loop */
+4:
+ mov r0, sp
+ mov r1, #0 /* Thread loop */
+ add r2, sp, #8
+ bl thread_loop
+ b 4b
+
+ /* UNREACHABLE */
+ mov r0, #0
+ ldr r7, =SYS_exit
+ swi 0
+
+ .p2align 2
+ .type secondary_thread,%function
+ .code 32
+secondary_thread:
+ /*
+ * On entry, r0 is where we stashed our sync word and
+ * ack word (thread ID).
+ *
+ * Stash the sync word in r4, thread ID in r5.
+ */
+ mov r4, r0
+ ldr r5, [r0, #4]
+
+ /* Print a message */
+ movw r0, :lower16:.L.secondarymsg
+ movt r0, :upper16:.L.secondarymsg
+ ldr r1, =(.L.secondarymsgEnd - .L.secondarymsg - 1)
+ bl print
+
+ /* Acknowledge that we started */
+ add r0, r5, #1
+ str r0, [r4, #4]
+
+1:
+ mov r0, r4
+ mov r1, r5
+ add r2, r4, #8
+ bl thread_loop
+ b 1b
+
+ .p2align 2
+ .type thread_loop,%function
+ .code 32
+thread_loop:
+ push {r4, r5, r6, r7, r8, lr}
+
+ /*
+ * r0 == sync word
+ * r1 == thread ID
+ * r2 == shared word
+ */
+ mov r4, r0
+ mov r5, r1
+ mov r6, r2
+ bl lock_mutex_swp
+ str r5, [r6] /* Write the thread ID */
+ bl random_cycles
+
+ # Save off the now cycle count */
+ mov r8, r0
+
+ /* Print the thread ID and cycle count */
+ mov r0, r5
+ mov r1, #0
+ bl printnum
+
+ /* Separator */
+ movw r0, :lower16:.L.idsep
+ movt r0, :upper16:.L.idsep
+ ldr r1, =(.L.idsepEnd - .L.idsep - 1)
*** 249 LINES SKIPPED ***