[PATCH v3 13/17] selftests: riscv: add misaligned access testing

Clément Léger
2025-03-10T15:14:37Z

Now that the kernel can emulate misaligned access and control its
behavior, add a selftest for that. This selftest tests all the currently
emulated instruction (except for the RV32 compressed ones which are left
as a future exercise for a RV32 user). For the FPU instructions, all the
FPU registers are tested.

Signed-off-by: Clément Léger <cleger@rivosinc.com>
---
.../selftests/riscv/misaligned/.gitignore | 1 +
.../selftests/riscv/misaligned/Makefile | 12 +
.../selftests/riscv/misaligned/common.S | 33 +++
.../testing/selftests/riscv/misaligned/fpu.S | 180 +++++++++++++
tools/testing/selftests/riscv/misaligned/gp.S | 103 +++++++
.../selftests/riscv/misaligned/misaligned.c | 254 ++++++++++++++++++
6 files changed, 583 insertions(+)
create mode 100644 tools/testing/selftests/riscv/misaligned/.gitignore
create mode 100644 tools/testing/selftests/riscv/misaligned/Makefile
create mode 100644 tools/testing/selftests/riscv/misaligned/common.S
create mode 100644 tools/testing/selftests/riscv/misaligned/fpu.S
create mode 100644 tools/testing/selftests/riscv/misaligned/gp.S
create mode 100644 tools/testing/selftests/riscv/misaligned/misaligned.c

diff --git a/tools/testing/selftests/riscv/misaligned/.gitignore b/tools/testing/selftests/riscv/misaligned/.gitignore
new file mode 100644
index 000000000000..5eff15a1f981
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/.gitignore
@@ -0,0 +1 @@
+misaligned
diff --git a/tools/testing/selftests/riscv/misaligned/Makefile b/tools/testing/selftests/riscv/misaligned/Makefile
new file mode 100644
index 000000000000..1aa40110c50d
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 ARM Limited
+# Originally tools/testing/arm64/abi/Makefile
+
+CFLAGS += -I$(top_srcdir)/tools/include
+
+TEST_GEN_PROGS := misaligned
+
+include ../../lib.mk
+
+$(OUTPUT)/misaligned: misaligned.c fpu.S gp.S
+ $(CC) -g3 -static -o$@ -march=rv64imafdc $(CFLAGS) $(LDFLAGS) $^
diff --git a/tools/testing/selftests/riscv/misaligned/common.S b/tools/testing/selftests/riscv/misaligned/common.S
new file mode 100644
index 000000000000..8fa00035bd5d
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/common.S
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Rivos Inc.
+ *
+ * Authors:
+ * Clément Léger <cleger@rivosinc.com>
+ */
+
+.macro lb_sb temp, offset, src, dst
+ lb \temp, \offset(\src)
+ sb \temp, \offset(\dst)
+.endm
+
+.macro copy_long_to temp, src, dst
+ lb_sb \temp, 0, \src, \dst,
+ lb_sb \temp, 1, \src, \dst,
+ lb_sb \temp, 2, \src, \dst,
+ lb_sb \temp, 3, \src, \dst,
+ lb_sb \temp, 4, \src, \dst,
+ lb_sb \temp, 5, \src, \dst,
+ lb_sb \temp, 6, \src, \dst,
+ lb_sb \temp, 7, \src, \dst,
+.endm
+
+.macro sp_stack_prologue offset
+ addi sp, sp, -8
+ sub sp, sp, \offset
+.endm
+
+.macro sp_stack_epilogue offset
+ add sp, sp, \offset
+ addi sp, sp, 8
+.endm
diff --git a/tools/testing/selftests/riscv/misaligned/fpu.S b/tools/testing/selftests/riscv/misaligned/fpu.S
new file mode 100644
index 000000000000..d008bff58310
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/fpu.S
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Rivos Inc.
+ *
+ * Authors:
+ * Clément Léger <cleger@rivosinc.com>
+ */
+
+#include "common.S"
+
+#define CASE_ALIGN 4
+
+.macro fpu_load_inst fpreg, inst, precision, load_reg
+.align CASE_ALIGN
+ \inst \fpreg, 0(\load_reg)
+ fmv.\precision fa0, \fpreg
+ j 2f
+.endm
+
+#define flw(__fpreg) fpu_load_inst __fpreg, flw, s, s1
+#define fld(__fpreg) fpu_load_inst __fpreg, fld, d, s1
+#define c_flw(__fpreg) fpu_load_inst __fpreg, c.flw, s, s1
+#define c_fld(__fpreg) fpu_load_inst __fpreg, c.fld, d, s1
+#define c_fldsp(__fpreg) fpu_load_inst __fpreg, c.fldsp, d, sp
+
+.macro fpu_store_inst fpreg, inst, precision, store_reg
+.align CASE_ALIGN
+ fmv.\precision \fpreg, fa0
+ \inst \fpreg, 0(\store_reg)
+ j 2f
+.endm
+
+#define fsw(__fpreg) fpu_store_inst __fpreg, fsw, s, s1
+#define fsd(__fpreg) fpu_store_inst __fpreg, fsd, d, s1
+#define c_fsw(__fpreg) fpu_store_inst __fpreg, c.fsw, s, s1
+#define c_fsd(__fpreg) fpu_store_inst __fpreg, c.fsd, d, s1
+#define c_fsdsp(__fpreg) fpu_store_inst __fpreg, c.fsdsp, d, sp
+
+.macro fp_test_prologue
+ move s1, a1
+ /*
+ * Compute jump offset to store the correct FP register since we don't
+ * have indirect FP register access (or at least we don't use this
+ * extension so that works on all archs)
+ */
+ sll t0, a0, CASE_ALIGN
+ la t2, 1f
+ add t0, t0, t2
+ jr t0
+.align CASE_ALIGN
+1:
+.endm
+
+.macro fp_test_prologue_compressed
+ /* FP registers for compressed instructions starts from 8 to 16 */
+ addi a0, a0, -8
+ fp_test_prologue
+.endm
+
+#define fp_test_body_compressed(__inst_func) \
+ __inst_func(f8); \
+ __inst_func(f9); \
+ __inst_func(f10); \
+ __inst_func(f11); \
+ __inst_func(f12); \
+ __inst_func(f13); \
+ __inst_func(f14); \
+ __inst_func(f15); \
+2:
+
+#define fp_test_body(__inst_func) \
+ __inst_func(f0); \
+ __inst_func(f1); \
+ __inst_func(f2); \
+ __inst_func(f3); \
+ __inst_func(f4); \
+ __inst_func(f5); \
+ __inst_func(f6); \
+ __inst_func(f7); \
+ __inst_func(f8); \
+ __inst_func(f9); \
+ __inst_func(f10); \
+ __inst_func(f11); \
+ __inst_func(f12); \
+ __inst_func(f13); \
+ __inst_func(f14); \
+ __inst_func(f15); \
+ __inst_func(f16); \
+ __inst_func(f17); \
+ __inst_func(f18); \
+ __inst_func(f19); \
+ __inst_func(f20); \
+ __inst_func(f21); \
+ __inst_func(f22); \
+ __inst_func(f23); \
+ __inst_func(f24); \
+ __inst_func(f25); \
+ __inst_func(f26); \
+ __inst_func(f27); \
+ __inst_func(f28); \
+ __inst_func(f29); \
+ __inst_func(f30); \
+ __inst_func(f31); \
+2:
+.text
+
+#define __gen_test_inst(__inst, __suffix) \
+.global test_ ## __inst; \
+test_ ## __inst:; \
+ fp_test_prologue ## __suffix; \
+ fp_test_body ## __suffix(__inst); \
+ ret
+
+#define gen_test_inst_compressed(__inst) \
+ .option arch,+c; \
+ __gen_test_inst(c_ ## __inst, _compressed)
+
+#define gen_test_inst(__inst) \
+ .balign 16; \
+ .option push; \
+ .option arch,-c; \
+ __gen_test_inst(__inst, ); \
+ .option pop
+
+.macro fp_test_prologue_load_compressed_sp
+ copy_long_to t0, a1, sp
+.endm
+
+.macro fp_test_epilogue_load_compressed_sp
+.endm
+
+.macro fp_test_prologue_store_compressed_sp
+.endm
+
+.macro fp_test_epilogue_store_compressed_sp
+ copy_long_to t0, sp, a1
+.endm
+
+#define gen_inst_compressed_sp(__inst, __type) \
+ .global test_c_ ## __inst ## sp; \
+ test_c_ ## __inst ## sp:; \
+ sp_stack_prologue a2; \
+ fp_test_prologue_## __type ## _compressed_sp; \
+ fp_test_prologue_compressed; \
+ fp_test_body_compressed(c_ ## __inst ## sp); \
+ fp_test_epilogue_## __type ## _compressed_sp; \
+ sp_stack_epilogue a2; \
+ ret
+
+#define gen_test_load_compressed_sp(__inst) gen_inst_compressed_sp(__inst, load)
+#define gen_test_store_compressed_sp(__inst) gen_inst_compressed_sp(__inst, store)
+
+/*
+ * float_fsw_reg - Set a FP register from a register containing the value
+ * a0 = FP register index to be set
+ * a1 = addr where to store register value
+ * a2 = address offset
+ * a3 = value to be store
+ */
+gen_test_inst(fsw)
+
+/*
+ * float_flw_reg - Get a FP register value and return it
+ * a0 = FP register index to be retrieved
+ * a1 = addr to load register from
+ * a2 = address offset
+ */
+gen_test_inst(flw)
+
+gen_test_inst(fsd)
+#ifdef __riscv_compressed
+gen_test_inst_compressed(fsd)
+gen_test_store_compressed_sp(fsd)
+#endif
+
+gen_test_inst(fld)
+#ifdef __riscv_compressed
+gen_test_inst_compressed(fld)
+gen_test_load_compressed_sp(fld)
+#endif
diff --git a/tools/testing/selftests/riscv/misaligned/gp.S b/tools/testing/selftests/riscv/misaligned/gp.S
new file mode 100644
index 000000000000..f53f4c6d81dd
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/gp.S
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2025 Rivos Inc.
+ *
+ * Authors:
+ * Clément Léger <cleger@rivosinc.com>
+ */
+
+#include "common.S"
+
+.text
+
+.macro __gen_test_inst inst, src_reg
+ \inst a2, 0(\src_reg)
+ move a0, a2
+.endm
+
+.macro gen_func_header func_name, rvc
+ .option arch,\rvc
+ .global test_\func_name
+ test_\func_name:
+.endm
+
+.macro gen_test_inst inst
+ .option push
+ gen_func_header \inst, -c
+ __gen_test_inst \inst, a0
+ .option pop
+ ret
+.endm
+
+.macro __gen_test_inst_c name, src_reg
+ .option push
+ gen_func_header c_\name, +c
+ __gen_test_inst c.\name, \src_reg
+ .option pop
+ ret
+.endm
+
+.macro gen_test_inst_c name
+ __gen_test_inst_c \name, a0
+.endm
+
+
+.macro gen_test_inst_load_c_sp name
+ .option push
+ gen_func_header c_\name\()sp, +c
+ sp_stack_prologue a1
+ copy_long_to t0, a0, sp
+ c.ldsp a0, 0(sp)
+ sp_stack_epilogue a1
+ .option pop
+ ret
+.endm
+
+.macro lb_sp_sb_a0 reg, offset
+ lb_sb \reg, \offset, sp, a0
+.endm
+
+.macro gen_test_inst_store_c_sp inst_name
+ .option push
+ gen_func_header c_\inst_name\()sp, +c
+ /* Misalign stack pointer */
+ sp_stack_prologue a1
+ /* Misalign access */
+ c.sdsp a2, 0(sp)
+ copy_long_to t0, sp, a0
+ sp_stack_epilogue a1
+ .option pop
+ ret
+.endm
+
+
+ /*
+ * a0 = addr to load from
+ * a1 = address offset
+ * a2 = value to be loaded
+ */
+gen_test_inst lh
+gen_test_inst lhu
+gen_test_inst lw
+gen_test_inst lwu
+gen_test_inst ld
+#ifdef __riscv_compressed
+gen_test_inst_c lw
+gen_test_inst_c ld
+gen_test_inst_load_c_sp ld
+#endif
+
+/*
+ * a0 = addr where to store value
+ * a1 = address offset
+ * a2 = value to be stored
+ */
+gen_test_inst sh
+gen_test_inst sw
+gen_test_inst sd
+#ifdef __riscv_compressed
+gen_test_inst_c sw
+gen_test_inst_c sd
+gen_test_inst_store_c_sp sd
+#endif
+
diff --git a/tools/testing/selftests/riscv/misaligned/misaligned.c b/tools/testing/selftests/riscv/misaligned/misaligned.c
new file mode 100644
index 000000000000..c66aa87ec03e
--- /dev/null
+++ b/tools/testing/selftests/riscv/misaligned/misaligned.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Rivos Inc.
+ *
+ * Authors:
+ * Clément Léger <cleger@rivosinc.com>
+ */
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <linux/ptrace.h>
+#include "../../kselftest_harness.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <float.h>
+#include <errno.h>
+#include <math.h>
+#include <string.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <ucontext.h>
+
+#include <sys/prctl.h>
+
+#define stringify(s) __stringify(s)
+#define __stringify(s) #s
+
+#define VAL16 0x1234
+#define VAL32 0xDEADBEEF
+#define VAL64 0x45674321D00DF789
+
+#define VAL_float 78951.234375
+#define VAL_double 567890.512396965789589290
+
+static bool float_equal(float a, float b)
+{
+ float scaled_epsilon;
+ float difference = fabsf(a - b);
+
+ // Scale to the largest value.
+ a = fabsf(a);
+ b = fabsf(b);
+ if (a > b)
+ scaled_epsilon = FLT_EPSILON * a;
+ else
+ scaled_epsilon = FLT_EPSILON * b;
+
+ return difference <= scaled_epsilon;
+}
+
+static bool double_equal(double a, double b)
+{
+ double scaled_epsilon;
+ double difference = fabsf(a - b);
+
+ // Scale to the largest value.
+ a = fabs(a);
+ b = fabs(b);
+ if (a > b)
+ scaled_epsilon = DBL_EPSILON * a;
+ else
+ scaled_epsilon = DBL_EPSILON * b;
+
+ return difference <= scaled_epsilon;
+}
+
+#define fpu_load_proto(__inst, __type) \
+extern __type test_ ## __inst(unsigned long fp_reg, void *addr, unsigned long offset, __type value)
+
+fpu_load_proto(flw, float);
+fpu_load_proto(fld, double);
+fpu_load_proto(c_flw, float);
+fpu_load_proto(c_fld, double);
+fpu_load_proto(c_fldsp, double);
+
+#define fpu_store_proto(__inst, __type) \
+extern void test_ ## __inst(unsigned long fp_reg, void *addr, unsigned long offset, __type value)
+
+fpu_store_proto(fsw, float);
+fpu_store_proto(fsd, double);
+fpu_store_proto(c_fsw, float);
+fpu_store_proto(c_fsd, double);
+fpu_store_proto(c_fsdsp, double);
+
+#define gp_load_proto(__inst, __type) \
+extern __type test_ ## __inst(void *addr, unsigned long offset, __type value)
+
+gp_load_proto(lh, uint16_t);
+gp_load_proto(lhu, uint16_t);
+gp_load_proto(lw, uint32_t);
+gp_load_proto(lwu, uint32_t);
+gp_load_proto(ld, uint64_t);
+gp_load_proto(c_lw, uint32_t);
+gp_load_proto(c_ld, uint64_t);
+gp_load_proto(c_ldsp, uint64_t);
+
+#define gp_store_proto(__inst, __type) \
+extern void test_ ## __inst(void *addr, unsigned long offset, __type value)
+
+gp_store_proto(sh, uint16_t);
+gp_store_proto(sw, uint32_t);
+gp_store_proto(sd, uint64_t);
+gp_store_proto(c_sw, uint32_t);
+gp_store_proto(c_sd, uint64_t);
+gp_store_proto(c_sdsp, uint64_t);
+
+#define TEST_GP_LOAD(__inst, __type_size) \
+TEST(gp_load_ ## __inst) \
+{ \
+ int offset, ret; \
+ uint8_t buf[16] __attribute__((aligned(16))); \
+ \
+ ret = prctl(PR_SET_UNALIGN, PR_UNALIGN_NOPRINT); \
+ ASSERT_EQ(ret, 0); \
+ \
+ for (offset = 1; offset < __type_size / 8; offset++) { \
+ uint ## __type_size ## _t val = VAL ## __type_size; \
+ uint ## __type_size ## _t *ptr = (uint ## __type_size ## _t *) (buf + offset); \
+ memcpy(ptr, &val, sizeof(val)); \
+ val = test_ ## __inst(ptr, offset, val); \
+ EXPECT_EQ(VAL ## __type_size, val); \
+ } \
+}
+
+TEST_GP_LOAD(lh, 16);
+TEST_GP_LOAD(lhu, 16);
+TEST_GP_LOAD(lw, 32);
+TEST_GP_LOAD(lwu, 32);
+TEST_GP_LOAD(ld, 64);
+#ifdef __riscv_compressed
+TEST_GP_LOAD(c_lw, 32);
+TEST_GP_LOAD(c_ld, 64);
+TEST_GP_LOAD(c_ldsp, 64);
+#endif
+
+#define TEST_GP_STORE(__inst, __type_size) \
+TEST(gp_load_ ## __inst) \
+{ \
+ int offset, ret; \
+ uint8_t buf[16] __attribute__((aligned(16))); \
+ \
+ ret = prctl(PR_SET_UNALIGN, PR_UNALIGN_NOPRINT); \
+ ASSERT_EQ(ret, 0); \
+ \
+ for (offset = 1; offset < __type_size / 8; offset++) { \
+ uint ## __type_size ## _t val = VAL ## __type_size; \
+ uint ## __type_size ## _t *ptr = (uint ## __type_size ## _t *) (buf + offset); \
+ memset(ptr, 0, sizeof(val)); \
+ test_ ## __inst(ptr, offset, val); \
+ memcpy(&val, ptr, sizeof(val)); \
+ EXPECT_EQ(VAL ## __type_size, val); \
+ } \
+}
+TEST_GP_STORE(sh, 16);
+TEST_GP_STORE(sw, 32);
+TEST_GP_STORE(sd, 64);
+#ifdef __riscv_compressed
+TEST_GP_STORE(c_sw, 32);
+TEST_GP_STORE(c_sd, 64);
+TEST_GP_STORE(c_sdsp, 64);
+#endif
+
+#define __TEST_FPU_LOAD(__type, __inst, __reg_start, __reg_end) \
+TEST(fpu_load_ ## __inst) \
+{ \
+ int i, ret, offset, fp_reg; \
+ uint8_t buf[16] __attribute__((aligned(16))); \
+ \
+ ret = prctl(PR_SET_UNALIGN, PR_UNALIGN_NOPRINT); \
+ ASSERT_EQ(ret, 0); \
+ \
+ for (fp_reg = __reg_start; fp_reg < __reg_end; fp_reg++) { \
+ for (offset = 1; offset < 4; offset++) { \
+ void *load_addr = (buf + offset); \
+ __type val = VAL_ ## __type ; \
+ \
+ memcpy(load_addr, &val, sizeof(val)); \
+ val = test_ ## __inst(fp_reg, load_addr, offset, val); \
+ EXPECT_TRUE(__type ##_equal(val, VAL_## __type)); \
+ } \
+ } \
+}
+#define TEST_FPU_LOAD(__type, __inst) \
+ __TEST_FPU_LOAD(__type, __inst, 0, 32)
+#define TEST_FPU_LOAD_COMPRESSED(__type, __inst) \
+ __TEST_FPU_LOAD(__type, __inst, 8, 16)
+
+TEST_FPU_LOAD(float, flw)
+TEST_FPU_LOAD(double, fld)
+#ifdef __riscv_compressed
+TEST_FPU_LOAD_COMPRESSED(double, c_fld)
+TEST_FPU_LOAD_COMPRESSED(double, c_fldsp)
+#endif
+
+#define __TEST_FPU_STORE(__type, __inst, __reg_start, __reg_end) \
+TEST(fpu_store_ ## __inst) \
+{ \
+ int i, ret, offset, fp_reg; \
+ uint8_t buf[16] __attribute__((aligned(16))); \
+ \
+ ret = prctl(PR_SET_UNALIGN, PR_UNALIGN_NOPRINT); \
+ ASSERT_EQ(ret, 0); \
+ \
+ for (fp_reg = __reg_start; fp_reg < __reg_end; fp_reg++) { \
+ for (offset = 1; offset < 4; offset++) { \
+ \
+ void *store_addr = (buf + offset); \
+ __type val = VAL_ ## __type ; \
+ \
+ test_ ## __inst(fp_reg, store_addr, offset, val); \
+ memcpy(&val, store_addr, sizeof(val)); \
+ EXPECT_TRUE(__type ## _equal(val, VAL_## __type)); \
+ } \
+ } \
+}
+#define TEST_FPU_STORE(__type, __inst) \
+ __TEST_FPU_STORE(__type, __inst, 0, 32)
+#define TEST_FPU_STORE_COMPRESSED(__type, __inst) \
+ __TEST_FPU_STORE(__type, __inst, 8, 16)
+
+TEST_FPU_STORE(float, fsw)
+TEST_FPU_STORE(double, fsd)
+#ifdef __riscv_compressed
+TEST_FPU_STORE_COMPRESSED(double, c_fsd)
+TEST_FPU_STORE_COMPRESSED(double, c_fsdsp)
+#endif
+
+TEST_SIGNAL(gen_sigbus, SIGBUS)
+{
+ uint32_t *ptr;
+ uint8_t buf[16] __attribute__((aligned(16)));
+ int ret;
+
+ ret = prctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS);
+ ASSERT_EQ(ret, 0);
+
+ ptr = (uint32_t *)(buf + 1);
+ *ptr = 0xDEADBEEFULL;
+}
+
+int main(int argc, char **argv)
+{
+ int ret, val;
+
+ ret = prctl(PR_GET_UNALIGN, &val);
+ if (ret == -1 && errno == EINVAL)
+ ksft_exit_skip("SKIP GET_UNALIGN_CTL not supported\n");
+
+ exit(test_harness_run(argc, argv));
+}
--
2.47.2