diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /tools/testing/selftests/riscv | |
| download | linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip | |
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe:
- Fix a task_work add use-after-free with SQPOLL.
The sqpoll thread could pop and complete the last request while
io_req_normal_work_add() was still looking at them after the mpscq
push.
Use the same approach as DEFER_TASKRUN to protect from that, holding
an RCU read lock across the add, and have exit wait for an RCU grace
period for SQPOLL rings as well.
- CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the
big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED
rings
- Mark the source filter table as COW when cloning bpf filters, so
registering another filter on the source doesn't modify the shared
table in place
- Initialize the task context before running the BPF loop
- Requeue zcrx multishot receives stopped by a local resource
- End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit)
* tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux:
io_uring: fix task_work add use-after-free with SQPOLL
io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full
io_uring/zcrx: requeue multishot receives stopped by a local resource
io_uring: initialize task context before running the BPF loop
io_uring: zero big_cqe for aux CQEs on CQE32 rings
io_uring: fix free entry check for 32b CQEs on CQE32 rings
io_uring: only post the dummy skip CQE on CQE_MIXED rings
io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'tools/testing/selftests/riscv')
37 files changed, 3669 insertions, 0 deletions
diff --git a/tools/testing/selftests/riscv/Makefile b/tools/testing/selftests/riscv/Makefile new file mode 100644 index 000000000..5671b4405 --- /dev/null +++ b/tools/testing/selftests/riscv/Makefile @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0 +# Originally tools/testing/arm64/Makefile + +# When ARCH not overridden for crosscompiling, lookup machine +ARCH ?= $(shell uname -m 2>/dev/null || echo not) + +ifneq (,$(filter $(ARCH),riscv)) +RISCV_SUBTARGETS ?= abi hwprobe mm sigreturn vector cfi +else +RISCV_SUBTARGETS := +endif + +CFLAGS := -Wall -O2 -g + +# A proper top_srcdir is needed by KSFT(lib.mk) +top_srcdir = $(realpath ../../../../) + +# Additional include paths needed by kselftest.h and local headers +CFLAGS += -I$(top_srcdir)/tools/testing/selftests/ + +CFLAGS += $(KHDR_INCLUDES) + +export CFLAGS +export top_srcdir + +all: + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + mkdir -p $$BUILD_TARGET; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +install: all + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +run_tests: all + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +# Avoid any output on non riscv on emit_tests +emit_tests: + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +clean: + @for DIR in $(RISCV_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + $(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +.PHONY: all clean install run_tests emit_tests diff --git a/tools/testing/selftests/riscv/README b/tools/testing/selftests/riscv/README new file mode 100644 index 000000000..443da395d --- /dev/null +++ b/tools/testing/selftests/riscv/README @@ -0,0 +1,24 @@ +KSelfTest RISC-V +================ + +- These tests are riscv specific and so not built or run but just skipped + completely when env-variable ARCH is found to be different than 'riscv'. + +- Holding true the above, RISC-V KSFT tests can be run within the + KSelfTest framework using standard Linux top-level-makefile targets: + + $ make TARGETS=riscv kselftest-clean + $ make TARGETS=riscv kselftest + + or + + $ make -C tools/testing/selftests TARGETS=riscv \ + INSTALL_PATH=<your-installation-path> install + + or, alternatively, only specific riscv/ subtargets can be picked: + + $ make -C tools/testing/selftests TARGETS=riscv RISCV_SUBTARGETS="mm vector" \ + INSTALL_PATH=<your-installation-path> install + + Further details on building and running KSFT can be found in: + Documentation/dev-tools/kselftest.rst diff --git a/tools/testing/selftests/riscv/abi/.gitignore b/tools/testing/selftests/riscv/abi/.gitignore new file mode 100644 index 000000000..b38358f91 --- /dev/null +++ b/tools/testing/selftests/riscv/abi/.gitignore @@ -0,0 +1 @@ +pointer_masking diff --git a/tools/testing/selftests/riscv/abi/Makefile b/tools/testing/selftests/riscv/abi/Makefile new file mode 100644 index 000000000..041114675 --- /dev/null +++ b/tools/testing/selftests/riscv/abi/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += -I$(top_srcdir)/tools/include + +TEST_GEN_PROGS := pointer_masking + +include ../../lib.mk + +$(OUTPUT)/pointer_masking: pointer_masking.c + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/abi/pointer_masking.c b/tools/testing/selftests/riscv/abi/pointer_masking.c new file mode 100644 index 000000000..2d540af7b --- /dev/null +++ b/tools/testing/selftests/riscv/abi/pointer_masking.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <errno.h> +#include <fcntl.h> +#include <setjmp.h> +#include <signal.h> +#include <stdbool.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "kselftest.h" + +#ifndef PR_PMLEN_SHIFT +#define PR_PMLEN_SHIFT 24 +#endif +#ifndef PR_PMLEN_MASK +#define PR_PMLEN_MASK (0x7fUL << PR_PMLEN_SHIFT) +#endif + +static int dev_zero; + +static int pipefd[2]; + +static sigjmp_buf jmpbuf; + +static void sigsegv_handler(int sig) +{ + siglongjmp(jmpbuf, 1); +} + +static int min_pmlen; +static int max_pmlen; + +static inline bool valid_pmlen(int pmlen) +{ + return pmlen == 0 || pmlen == 7 || pmlen == 16; +} + +static void test_pmlen(void) +{ + ksft_print_msg("Testing available PMLEN values\n"); + + for (int request = 0; request <= 16; request++) { + int pmlen, ret; + + ret = prctl(PR_SET_TAGGED_ADDR_CTRL, request << PR_PMLEN_SHIFT, 0, 0, 0); + if (ret) + goto pr_set_error; + + ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); + ksft_test_result(ret >= 0, "PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request); + if (ret < 0) + goto pr_get_error; + + pmlen = (ret & PR_PMLEN_MASK) >> PR_PMLEN_SHIFT; + ksft_test_result(pmlen >= request, "PMLEN=%d constraint\n", request); + ksft_test_result(valid_pmlen(pmlen), "PMLEN=%d validity\n", request); + + if (min_pmlen == 0) + min_pmlen = pmlen; + if (max_pmlen < pmlen) + max_pmlen = pmlen; + + continue; + +pr_set_error: + ksft_test_result_skip("PMLEN=%d PR_GET_TAGGED_ADDR_CTRL\n", request); +pr_get_error: + ksft_test_result_skip("PMLEN=%d constraint\n", request); + ksft_test_result_skip("PMLEN=%d validity\n", request); + } + + if (max_pmlen == 0) + ksft_exit_fail_msg("Failed to enable pointer masking\n"); +} + +static int set_tagged_addr_ctrl(int pmlen, bool tagged_addr_abi) +{ + int arg, ret; + + arg = pmlen << PR_PMLEN_SHIFT | tagged_addr_abi; + ret = prctl(PR_SET_TAGGED_ADDR_CTRL, arg, 0, 0, 0); + if (!ret) { + ret = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); + if (ret == arg) + return 0; + } + + return ret < 0 ? -errno : -ENODATA; +} + +static void test_dereference_pmlen(int pmlen) +{ + static volatile int i; + volatile int *p; + int ret; + + ret = set_tagged_addr_ctrl(pmlen, false); + if (ret) + return ksft_test_result_error("PMLEN=%d setup (%d)\n", pmlen, ret); + + i = pmlen; + + if (pmlen) { + p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen)); + + /* These dereferences should succeed. */ + if (sigsetjmp(jmpbuf, 1)) + return ksft_test_result_fail("PMLEN=%d valid tag\n", pmlen); + if (*p != pmlen) + return ksft_test_result_fail("PMLEN=%d bad value\n", pmlen); + ++*p; + } + + p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1)); + + /* These dereferences should raise SIGSEGV. */ + if (sigsetjmp(jmpbuf, 1)) + return ksft_test_result_pass("PMLEN=%d dereference\n", pmlen); + ++*p; + ksft_test_result_fail("PMLEN=%d invalid tag\n", pmlen); +} + +static void test_dereference(void) +{ + ksft_print_msg("Testing userspace pointer dereference\n"); + + signal(SIGSEGV, sigsegv_handler); + + test_dereference_pmlen(0); + test_dereference_pmlen(min_pmlen); + test_dereference_pmlen(max_pmlen); + + signal(SIGSEGV, SIG_DFL); +} + +static void execve_child_sigsegv_handler(int sig) +{ + exit(42); +} + +static int execve_child(void) +{ + static volatile int i; + volatile int *p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - 7)); + + signal(SIGSEGV, execve_child_sigsegv_handler); + + /* This dereference should raise SIGSEGV. */ + return *p; +} + +static void test_fork_exec(void) +{ + int ret, status; + + ksft_print_msg("Testing fork/exec behavior\n"); + + ret = set_tagged_addr_ctrl(min_pmlen, false); + if (ret) + return ksft_test_result_error("setup (%d)\n", ret); + + if (fork()) { + wait(&status); + ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42, + "dereference after fork\n"); + } else { + static volatile int i = 42; + volatile int *p; + + p = (volatile int *)((uintptr_t)&i | 1UL << (__riscv_xlen - min_pmlen)); + + /* This dereference should succeed. */ + exit(*p); + } + + if (fork()) { + wait(&status); + ksft_test_result(WIFEXITED(status) && WEXITSTATUS(status) == 42, + "dereference after fork+exec\n"); + } else { + /* Will call execve_child(). */ + execve("/proc/self/exe", (char *const []) { "", NULL }, NULL); + } +} + +static bool pwrite_wrapper(int fd, void *buf, size_t count, const char *msg) +{ + int ret = pwrite(fd, buf, count, 0); + + if (ret != count) { + ksft_perror(msg); + return false; + } + return true; +} + +static void test_tagged_addr_abi_sysctl(void) +{ + char *err_pwrite_msg = "failed to write to /proc/sys/abi/tagged_addr_disabled\n"; + char value; + int fd; + + ksft_print_msg("Testing tagged address ABI sysctl\n"); + + fd = open("/proc/sys/abi/tagged_addr_disabled", O_WRONLY); + if (fd < 0) { + ksft_test_result_skip("failed to open sysctl file\n"); + ksft_test_result_skip("failed to open sysctl file\n"); + return; + } + + value = '1'; + if (!pwrite_wrapper(fd, &value, 1, "write '1'")) + ksft_test_result_fail(err_pwrite_msg); + else + ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == -EINVAL, + "sysctl disabled\n"); + + value = '0'; + if (!pwrite_wrapper(fd, &value, 1, "write '0'")) + ksft_test_result_fail(err_pwrite_msg); + else + ksft_test_result(set_tagged_addr_ctrl(min_pmlen, true) == 0, + "sysctl enabled\n"); + + set_tagged_addr_ctrl(0, false); + + close(fd); +} + +static void test_tagged_addr_abi_pmlen(int pmlen) +{ + int i, *p, ret; + + i = ~pmlen; + + if (pmlen) { + p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen)); + + ret = set_tagged_addr_ctrl(pmlen, false); + if (ret) + return ksft_test_result_error("PMLEN=%d ABI disabled setup (%d)\n", + pmlen, ret); + + ret = write(pipefd[1], p, sizeof(*p)); + if (ret >= 0 || errno != EFAULT) + return ksft_test_result_fail("PMLEN=%d ABI disabled write\n", pmlen); + + ret = read(dev_zero, p, sizeof(*p)); + if (ret >= 0 || errno != EFAULT) + return ksft_test_result_fail("PMLEN=%d ABI disabled read\n", pmlen); + + if (i != ~pmlen) + return ksft_test_result_fail("PMLEN=%d ABI disabled value\n", pmlen); + + ret = set_tagged_addr_ctrl(pmlen, true); + if (ret) + return ksft_test_result_error("PMLEN=%d ABI enabled setup (%d)\n", + pmlen, ret); + + ret = write(pipefd[1], p, sizeof(*p)); + if (ret != sizeof(*p)) + return ksft_test_result_fail("PMLEN=%d ABI enabled write\n", pmlen); + + ret = read(dev_zero, p, sizeof(*p)); + if (ret != sizeof(*p)) + return ksft_test_result_fail("PMLEN=%d ABI enabled read\n", pmlen); + + if (i) + return ksft_test_result_fail("PMLEN=%d ABI enabled value\n", pmlen); + + i = ~pmlen; + } else { + /* The tagged address ABI cannot be enabled when PMLEN == 0. */ + ret = set_tagged_addr_ctrl(pmlen, true); + if (ret != -EINVAL) + return ksft_test_result_error("PMLEN=%d ABI setup (%d)\n", + pmlen, ret); + } + + p = (int *)((uintptr_t)&i | 1UL << (__riscv_xlen - pmlen - 1)); + + ret = write(pipefd[1], p, sizeof(*p)); + if (ret >= 0 || errno != EFAULT) + return ksft_test_result_fail("PMLEN=%d invalid tag write (%d)\n", pmlen, errno); + + ret = read(dev_zero, p, sizeof(*p)); + if (ret >= 0 || errno != EFAULT) + return ksft_test_result_fail("PMLEN=%d invalid tag read\n", pmlen); + + if (i != ~pmlen) + return ksft_test_result_fail("PMLEN=%d invalid tag value\n", pmlen); + + ksft_test_result_pass("PMLEN=%d tagged address ABI\n", pmlen); +} + +static void test_tagged_addr_abi(void) +{ + ksft_print_msg("Testing tagged address ABI\n"); + + test_tagged_addr_abi_pmlen(0); + test_tagged_addr_abi_pmlen(min_pmlen); + test_tagged_addr_abi_pmlen(max_pmlen); +} + +static struct test_info { + unsigned int nr_tests; + void (*test_fn)(void); +} tests[] = { + { .nr_tests = 17 * 3, test_pmlen }, + { .nr_tests = 3, test_dereference }, + { .nr_tests = 2, test_fork_exec }, + { .nr_tests = 2, test_tagged_addr_abi_sysctl }, + { .nr_tests = 3, test_tagged_addr_abi }, +}; + +int main(int argc, char **argv) +{ + unsigned int plan = 0; + int ret; + + /* Check if this is the child process after execve(). */ + if (!argv[0][0]) + return execve_child(); + + dev_zero = open("/dev/zero", O_RDWR); + if (dev_zero < 0) + return 1; + + /* Write to a pipe so the kernel must dereference the buffer pointer. */ + ret = pipe(pipefd); + if (ret) + return 1; + + ksft_print_header(); + + for (int i = 0; i < ARRAY_SIZE(tests); i++) + plan += tests[i].nr_tests; + + ksft_set_plan(plan); + + for (int i = 0; i < ARRAY_SIZE(tests); i++) + tests[i].test_fn(); + + ksft_finished(); +} diff --git a/tools/testing/selftests/riscv/cfi/.gitignore b/tools/testing/selftests/riscv/cfi/.gitignore new file mode 100644 index 000000000..c1faf7ca4 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/.gitignore @@ -0,0 +1,2 @@ +cfitests +shadowstack diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile new file mode 100644 index 000000000..418b4b532 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += $(KHDR_INCLUDES) +CFLAGS += -I$(top_srcdir)/tools/include + +CFLAGS += -march=rv64gc_zicfilp_zicfiss -fcf-protection=full + +# Check for zicfi* extensions needs cross compiler +# which is not set until lib.mk is included +ifeq ($(LLVM)$(CC),cc) +CC := $(CROSS_COMPILE)gcc +endif + + +ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0) +TEST_GEN_PROGS := cfitests + +$(OUTPUT)/cfitests: cfitests.c shadowstack.c + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ +else + +$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2) +endif + +include ../../lib.mk diff --git a/tools/testing/selftests/riscv/cfi/cfi_rv_test.h b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h new file mode 100644 index 000000000..184df6903 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/cfi_rv_test.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SELFTEST_RISCV_CFI_H +#define SELFTEST_RISCV_CFI_H +#include <stddef.h> +#include <sys/types.h> +#include "shadowstack.h" + +#define CHILD_EXIT_CODE_SSWRITE 10 +#define CHILD_EXIT_CODE_SIG_TEST 11 + +#define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + register long _arg4 __asm__ ("a3") = (long)(arg4); \ + register long _arg5 __asm__ ("a4") = (long)(arg5); \ + \ + __asm__ volatile( \ + "ecall\n" \ + : "+r" \ + (_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_num) \ + : "memory", "cc" \ + ); \ + _arg1; \ +}) + +#define my_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("a7") = (num); \ + register long _arg1 __asm__ ("a0") = (long)(arg1); \ + register long _arg2 __asm__ ("a1") = (long)(arg2); \ + register long _arg3 __asm__ ("a2") = (long)(arg3); \ + \ + __asm__ volatile( \ + "ecall\n" \ + : "+r" (_arg1) \ + : "r"(_arg2), "r"(_arg3), \ + "r"(_num) \ + : "memory", "cc" \ + ); \ + _arg1; \ +}) + +#ifndef __NR_prctl +#define __NR_prctl 167 +#endif + +#ifndef __NR_map_shadow_stack +#define __NR_map_shadow_stack 453 +#endif + +#define CSR_SSP 0x011 + +#ifdef __ASSEMBLER__ +#define __ASM_STR(x) x +#else +#define __ASM_STR(x) #x +#endif + +#define csr_read(csr) \ +({ \ + register unsigned long __v; \ + __asm__ __volatile__ ("csrr %0, " __ASM_STR(csr) \ + : "=r" (__v) : \ + : "memory"); \ + __v; \ +}) + +#define csr_write(csr, val) \ +({ \ + unsigned long __v = (unsigned long)(val); \ + __asm__ __volatile__ ("csrw " __ASM_STR(csr) ", %0" \ + : : "rK" (__v) \ + : "memory"); \ +}) + +#endif diff --git a/tools/testing/selftests/riscv/cfi/cfitests.c b/tools/testing/selftests/riscv/cfi/cfitests.c new file mode 100644 index 000000000..0e3943461 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/cfitests.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "../../kselftest.h" +#include <sys/signal.h> +#include <asm/ucontext.h> +#include <linux/prctl.h> +#include <errno.h> +#include <linux/ptrace.h> +#include <sys/wait.h> +#include <linux/elf.h> +#include <sys/uio.h> +#include <asm-generic/unistd.h> + +#include "cfi_rv_test.h" + +/* do not optimize cfi related test functions */ +#pragma GCC push_options +#pragma GCC optimize("O0") + +void sigsegv_handler(int signum, siginfo_t *si, void *uc) +{ + struct ucontext *ctx = (struct ucontext *)uc; + + if (si->si_code == SEGV_CPERR) { + ksft_print_msg("Control flow violation happened somewhere\n"); + ksft_print_msg("PC where violation happened %lx\n", ctx->uc_mcontext.gregs[0]); + exit(-1); + } + + /* all other cases are expected to be of shadow stack write case */ + exit(CHILD_EXIT_CODE_SSWRITE); +} + +bool register_signal_handler(void) +{ + struct sigaction sa = {}; + + sa.sa_sigaction = sigsegv_handler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) { + ksft_print_msg("Registering signal handler for landing pad violation failed\n"); + return false; + } + + return true; +} + +long ptrace(int request, pid_t pid, void *addr, void *data); + +bool cfi_ptrace_test(void) +{ + pid_t pid; + int status, ret = 0; + unsigned long ptrace_test_num = 0, total_ptrace_tests = 2; + + struct user_cfi_state cfi_reg; + struct iovec iov; + + pid = fork(); + + if (pid == -1) { + ksft_exit_fail_msg("%s: fork failed\n", __func__); + exit(1); + } + + if (pid == 0) { + /* allow to be traced */ + ptrace(PTRACE_TRACEME, 0, NULL, NULL); + raise(SIGSTOP); + asm volatile ("la a5, 1f\n" + "jalr a5\n" + "nop\n" + "nop\n" + "1: nop\n" + : : : "a5"); + exit(11); + /* child shouldn't go beyond here */ + } + + /* parent's code goes here */ + iov.iov_base = &cfi_reg; + iov.iov_len = sizeof(cfi_reg); + + while (ptrace_test_num < total_ptrace_tests) { + memset(&cfi_reg, 0, sizeof(cfi_reg)); + waitpid(pid, &status, 0); + if (WIFSTOPPED(status)) { + errno = 0; + ret = ptrace(PTRACE_GETREGSET, pid, (void *)NT_RISCV_USER_CFI, &iov); + if (ret == -1 && errno) + ksft_exit_fail_msg("%s: PTRACE_GETREGSET failed\n", __func__); + } else { + ksft_exit_fail_msg("%s: child didn't stop, failed\n", __func__); + } + + switch (ptrace_test_num) { +#define CFI_ENABLE_MASK (PTRACE_CFI_BRANCH_LANDING_PAD_EN_STATE | \ + PTRACE_CFI_SHADOW_STACK_EN_STATE | \ + PTRACE_CFI_SHADOW_STACK_PTR_STATE) + case 0: + if ((cfi_reg.cfi_status.cfi_state & CFI_ENABLE_MASK) != CFI_ENABLE_MASK) + ksft_exit_fail_msg("%s: ptrace_getregset failed, %llu\n", __func__, + cfi_reg.cfi_status.cfi_state); + if (!cfi_reg.shstk_ptr) + ksft_exit_fail_msg("%s: NULL shadow stack pointer, test failed\n", + __func__); + break; + case 1: + if (!(cfi_reg.cfi_status.cfi_state & + PTRACE_CFI_BRANCH_EXPECTED_LANDING_PAD_STATE)) + ksft_exit_fail_msg("%s: elp must have been set\n", __func__); + /* clear elp state. not interested in anything else */ + cfi_reg.cfi_status.cfi_state = 0; + + ret = ptrace(PTRACE_SETREGSET, pid, (void *)NT_RISCV_USER_CFI, &iov); + if (ret == -1 && errno) + ksft_exit_fail_msg("%s: PTRACE_GETREGSET failed\n", __func__); + break; + default: + ksft_exit_fail_msg("%s: unreachable switch case\n", __func__); + break; + } + ptrace(PTRACE_CONT, pid, NULL, NULL); + ptrace_test_num++; + } + + waitpid(pid, &status, 0); + if (WEXITSTATUS(status) != 11) + ksft_print_msg("%s, bad return code from child\n", __func__); + + ksft_print_msg("%s, ptrace test succeeded\n", __func__); + return true; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + unsigned long lpad_status = 0, ss_status = 0; + + ksft_print_header(); + + ksft_print_msg("Starting risc-v tests\n"); + + /* Test unknown PR_CFI bits */ + ret = my_syscall5(__NR_prctl, PR_SET_CFI, PR_CFI_BRANCH_LANDING_PADS, + PR_CFI_ENABLE | 0xffff0, 0, 0); + if (!ret) + ksft_exit_fail_msg("PR_SET_CFI accepted reserved branch landing pad bits\n"); + + /* + * Landing pad test. Not a lot of kernel changes to support landing + * pads for user mode except lighting up a bit in senvcfg via a prctl. + * Enable landing pad support throughout the execution of the test binary. + */ + ret = my_syscall5(__NR_prctl, PR_GET_CFI, PR_CFI_BRANCH_LANDING_PADS, &lpad_status, 0, 0); + if (ret) + ksft_exit_fail_msg("Get landing pad status failed with %d\n", ret); + + if (!(lpad_status & PR_CFI_ENABLE)) + ksft_exit_fail_msg("Landing pad is not enabled, should be enabled via glibc\n"); + + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); + if (ret) + ksft_exit_fail_msg("Get shadow stack failed with %d\n", ret); + + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) + ksft_exit_fail_msg("Shadow stack is not enabled, should be enabled via glibc\n"); + + if (!register_signal_handler()) + ksft_exit_fail_msg("Registering signal handler for SIGSEGV failed\n"); + + ksft_print_msg("Landing pad and shadow stack are enabled for binary\n"); + cfi_ptrace_test(); + + execute_shadow_stack_tests(); + + return 0; +} + +#pragma GCC pop_options diff --git a/tools/testing/selftests/riscv/cfi/shadowstack.c b/tools/testing/selftests/riscv/cfi/shadowstack.c new file mode 100644 index 000000000..f8eed8260 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/shadowstack.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "../../kselftest.h" +#include <sys/wait.h> +#include <signal.h> +#include <fcntl.h> +#include <asm-generic/unistd.h> +#include <sys/mman.h> +#include "shadowstack.h" +#include "cfi_rv_test.h" + +static struct shadow_stack_tests shstk_tests[] = { + { "shstk fork test\n", shadow_stack_fork_test }, + { "map shadow stack syscall\n", shadow_stack_map_test }, + { "shadow stack gup tests\n", shadow_stack_gup_tests }, + { "shadow stack signal tests\n", shadow_stack_signal_test}, + { "memory protections of shadow stack memory\n", shadow_stack_protection_test } +}; + +#define RISCV_SHADOW_STACK_TESTS ARRAY_SIZE(shstk_tests) + +/* do not optimize shadow stack related test functions */ +#pragma GCC push_options +#pragma GCC optimize("O0") + +void zar(void) +{ + unsigned long ssp = 0; + + ssp = csr_read(CSR_SSP); + ksft_print_msg("Spewing out shadow stack ptr: %lx\n" + " This is to ensure shadow stack is indeed enabled and working\n", + ssp); +} + +void bar(void) +{ + zar(); +} + +void foo(void) +{ + bar(); +} + +void zar_child(void) +{ + unsigned long ssp = 0; + + ssp = csr_read(CSR_SSP); + ksft_print_msg("Spewing out shadow stack ptr: %lx\n" + " This is to ensure shadow stack is indeed enabled and working\n", + ssp); +} + +void bar_child(void) +{ + zar_child(); +} + +void foo_child(void) +{ + bar_child(); +} + +typedef void (call_func_ptr)(void); +/* + * call couple of functions to test push/pop. + */ +int shadow_stack_call_tests(call_func_ptr fn_ptr, bool parent) +{ + ksft_print_msg("dummy calls for sspush and sspopchk in context of %s\n", + parent ? "parent" : "child"); + + (fn_ptr)(); + + return 0; +} + +/* forks a thread, and ensure shadow stacks fork out */ +bool shadow_stack_fork_test(unsigned long test_num, void *ctx) +{ + int pid = 0, child_status = 0, parent_pid = 0, ret = 0; + unsigned long ss_status = 0; + + ksft_print_msg("Exercising shadow stack fork test\n"); + + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); + if (ret) { + ksft_exit_skip("Shadow stack get status prctl failed with errorcode %d\n", ret); + return false; + } + + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) + ksft_exit_skip("Shadow stack is not enabled, should be enabled via glibc\n"); + + parent_pid = getpid(); + pid = fork(); + + if (pid) { + ksft_print_msg("Parent pid %d and child pid %d\n", parent_pid, pid); + shadow_stack_call_tests(&foo, true); + } else { + shadow_stack_call_tests(&foo_child, false); + } + + if (pid) { + ksft_print_msg("Waiting on child to finish\n"); + wait(&child_status); + } else { + /* exit child gracefully */ + exit(0); + } + + if (pid && WIFSIGNALED(child_status)) { + ksft_print_msg("Child faulted, fork test failed\n"); + return false; + } + + return true; +} + +/* exercise 'map_shadow_stack', pivot to it and call some functions to ensure it works */ +#define SHADOW_STACK_ALLOC_SIZE 4096 +bool shadow_stack_map_test(unsigned long test_num, void *ctx) +{ + unsigned long shdw_addr; + int ret = 0; + + ksft_print_msg("Exercising shadow stack map test\n"); + + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); + + if (((long)shdw_addr) <= 0) { + ksft_print_msg("map_shadow_stack failed with error code %d\n", + (int)shdw_addr); + return false; + } + + ret = munmap((void *)shdw_addr, SHADOW_STACK_ALLOC_SIZE); + + if (ret) { + ksft_print_msg("munmap failed with error code %d\n", ret); + return false; + } + + return true; +} + +/* + * shadow stack protection tests. map a shadow stack and + * validate all memory protections work on it + */ +bool shadow_stack_protection_test(unsigned long test_num, void *ctx) +{ + unsigned long shdw_addr; + unsigned long *write_addr = NULL; + int ret = 0, pid = 0, child_status = 0; + + ksft_print_msg("Exercising shadow stack protection test (WPT)\n"); + + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); + + if (((long)shdw_addr) <= 0) { + ksft_print_msg("map_shadow_stack failed with error code %d\n", + (int)shdw_addr); + return false; + } + + write_addr = (unsigned long *)shdw_addr; + pid = fork(); + + /* no child was created, return false */ + if (pid == -1) + return false; + + /* + * try to perform a store from child on shadow stack memory + * it should result in SIGSEGV + */ + if (!pid) { + /* below write must lead to SIGSEGV */ + *write_addr = 0xdeadbeef; + } else { + wait(&child_status); + } + + /* test fail, if 0xdeadbeef present on shadow stack address */ + if (*write_addr == 0xdeadbeef) { + ksft_print_msg("Shadow stack WPT failed\n"); + return false; + } + + /* if child reached here, then fail */ + if (!pid) { + ksft_print_msg("Shadow stack WPT failed: child reached unreachable state\n"); + return false; + } + + /* if child exited via signal handler but not for write on ss */ + if (WIFEXITED(child_status) && + WEXITSTATUS(child_status) != CHILD_EXIT_CODE_SSWRITE) { + ksft_print_msg("Shadow stack WPT failed: child wasn't signaled for write\n"); + return false; + } + + ret = munmap(write_addr, SHADOW_STACK_ALLOC_SIZE); + if (ret) { + ksft_print_msg("Shadow stack WPT failed: munmap failed, error code %d\n", + ret); + return false; + } + + return true; +} + +#define SS_MAGIC_WRITE_VAL 0xbeefdead + +int gup_tests(int mem_fd, unsigned long *shdw_addr) +{ + unsigned long val = 0; + + lseek(mem_fd, (unsigned long)shdw_addr, SEEK_SET); + if (read(mem_fd, &val, sizeof(val)) < 0) { + ksft_print_msg("Reading shadow stack mem via gup failed\n"); + return 1; + } + + val = SS_MAGIC_WRITE_VAL; + lseek(mem_fd, (unsigned long)shdw_addr, SEEK_SET); + if (write(mem_fd, &val, sizeof(val)) < 0) { + ksft_print_msg("Writing shadow stack mem via gup failed\n"); + return 1; + } + + if (*shdw_addr != SS_MAGIC_WRITE_VAL) { + ksft_print_msg("GUP write to shadow stack memory failed\n"); + return 1; + } + + return 0; +} + +bool shadow_stack_gup_tests(unsigned long test_num, void *ctx) +{ + unsigned long shdw_addr = 0; + unsigned long *write_addr = NULL; + int fd = 0; + bool ret = false; + + ksft_print_msg("Exercising shadow stack gup tests\n"); + shdw_addr = my_syscall3(__NR_map_shadow_stack, NULL, SHADOW_STACK_ALLOC_SIZE, 0); + + if (((long)shdw_addr) <= 0) { + ksft_print_msg("map_shadow_stack failed with error code %d\n", (int)shdw_addr); + return false; + } + + write_addr = (unsigned long *)shdw_addr; + + fd = open("/proc/self/mem", O_RDWR); + if (fd == -1) + return false; + + if (gup_tests(fd, write_addr)) { + ksft_print_msg("gup tests failed\n"); + goto out; + } + + ret = true; +out: + if (shdw_addr && munmap(write_addr, SHADOW_STACK_ALLOC_SIZE)) { + ksft_print_msg("munmap failed with error code %d\n", ret); + ret = false; + } + + return ret; +} + +volatile bool break_loop; + +void sigusr1_handler(int signo) +{ + break_loop = true; +} + +bool sigusr1_signal_test(void) +{ + struct sigaction sa = {}; + + sa.sa_handler = sigusr1_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, NULL)) { + ksft_print_msg("Registering signal handler for SIGUSR1 failed\n"); + return false; + } + + return true; +} + +/* + * shadow stack signal test. shadow stack must be enabled. + * register a signal, fork another thread which is waiting + * on signal. Send a signal from parent to child, verify + * that signal was received by child. If not test fails + */ +bool shadow_stack_signal_test(unsigned long test_num, void *ctx) +{ + int pid = 0, child_status = 0, ret = 0; + unsigned long ss_status = 0; + + ksft_print_msg("Exercising shadow stack signal test\n"); + + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_status, 0, 0, 0); + if (ret) { + ksft_print_msg("Shadow stack get status prctl failed with errorcode %d\n", ret); + return false; + } + + if (!(ss_status & PR_SHADOW_STACK_ENABLE)) + ksft_print_msg("Shadow stack is not enabled, should be enabled via glibc\n"); + + /* this should be caught by signal handler and do an exit */ + if (!sigusr1_signal_test()) { + ksft_print_msg("Registering sigusr1 handler failed\n"); + exit(-1); + } + + pid = fork(); + + if (pid == -1) { + ksft_print_msg("Signal test: fork failed\n"); + goto out; + } + + if (pid == 0) { + while (!break_loop) + sleep(1); + + exit(11); + /* child shouldn't go beyond here */ + } + + /* send SIGUSR1 to child */ + kill(pid, SIGUSR1); + wait(&child_status); + +out: + + return (WIFEXITED(child_status) && + WEXITSTATUS(child_status) == 11); +} + +int execute_shadow_stack_tests(void) +{ + int ret = 0; + unsigned long test_count = 0; + unsigned long shstk_status = 0; + bool test_pass = false; + + ksft_print_msg("Executing RISC-V shadow stack self tests\n"); + ksft_set_plan(RISCV_SHADOW_STACK_TESTS); + + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &shstk_status, 0, 0, 0); + + if (ret != 0) + ksft_exit_fail_msg("Get shadow stack status failed with %d\n", ret); + + /* + * If we are here that means get shadow stack status succeeded and + * thus shadow stack support is baked in the kernel. + */ + while (test_count < RISCV_SHADOW_STACK_TESTS) { + test_pass = (*shstk_tests[test_count].t_func)(test_count, NULL); + ksft_test_result(test_pass, shstk_tests[test_count].name); + test_count++; + } + + ksft_finished(); + + return 0; +} + +#pragma GCC pop_options diff --git a/tools/testing/selftests/riscv/cfi/shadowstack.h b/tools/testing/selftests/riscv/cfi/shadowstack.h new file mode 100644 index 000000000..943a36859 --- /dev/null +++ b/tools/testing/selftests/riscv/cfi/shadowstack.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SELFTEST_SHADOWSTACK_TEST_H +#define SELFTEST_SHADOWSTACK_TEST_H +#include <stddef.h> +#include <linux/prctl.h> + +/* + * A CFI test returns true for success or false for fail. + * Takes a test number to index into array, and a void pointer. + */ +typedef bool (*shstk_test_func)(unsigned long test_num, void *); + +struct shadow_stack_tests { + char *name; + shstk_test_func t_func; +}; + +bool shadow_stack_fork_test(unsigned long test_num, void *ctx); +bool shadow_stack_map_test(unsigned long test_num, void *ctx); +bool shadow_stack_protection_test(unsigned long test_num, void *ctx); +bool shadow_stack_gup_tests(unsigned long test_num, void *ctx); +bool shadow_stack_signal_test(unsigned long test_num, void *ctx); + +int execute_shadow_stack_tests(void); + +#endif diff --git a/tools/testing/selftests/riscv/hwprobe/.gitignore b/tools/testing/selftests/riscv/hwprobe/.gitignore new file mode 100644 index 000000000..6e384e80e --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/.gitignore @@ -0,0 +1,3 @@ +hwprobe +cbo +which-cpus diff --git a/tools/testing/selftests/riscv/hwprobe/Makefile b/tools/testing/selftests/riscv/hwprobe/Makefile new file mode 100644 index 000000000..71e3f26c5 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/Makefile @@ -0,0 +1,18 @@ +# 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 := hwprobe cbo which-cpus + +include ../../lib.mk + +$(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/cbo: cbo.c sys_hwprobe.S + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/which-cpus: which-cpus.c sys_hwprobe.S + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/hwprobe/cbo.c b/tools/testing/selftests/riscv/hwprobe/cbo.c new file mode 100644 index 000000000..f254b2edd --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/cbo.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Ventana Micro Systems Inc. + * + * Run with 'taskset -c <cpu-list> cbo' to only execute hwprobe on a + * subset of cpus, as well as only executing the tests on those cpus. + */ +#define _GNU_SOURCE +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <sched.h> +#include <signal.h> +#include <assert.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <asm/ucontext.h> +#include <getopt.h> + +#include "hwprobe.h" +#include "kselftest.h" + +#define MK_CBO(fn) le32_bswap((uint32_t)(fn) << 20 | 10 << 15 | 2 << 12 | 0 << 7 | 15) +#define MK_PREFETCH(fn) \ + le32_bswap(0 << 25 | (uint32_t)(fn) << 20 | 10 << 15 | 6 << 12 | 0 << 7 | 19) + +static char mem[4096] __aligned(4096) = { [0 ... 4095] = 0xa5 }; + +static bool got_fault; + +static void fault_handler(int sig, siginfo_t *info, void *context) +{ + unsigned long *regs = (unsigned long *)&((ucontext_t *)context)->uc_mcontext; + uint32_t insn = *(uint32_t *)regs[0]; + + if (sig == SIGILL) + assert(insn == MK_CBO(regs[11])); + + if (sig == SIGSEGV || sig == SIGBUS) + assert(insn == MK_PREFETCH(regs[11])); + + got_fault = true; + regs[0] += 4; +} + +#define cbo_insn(base, fn) \ +({ \ + asm volatile( \ + "mv a0, %0\n" \ + "li a1, %1\n" \ + ".4byte %2\n" \ + : : "r" (base), "i" (fn), "i" (MK_CBO(fn)) : "a0", "a1", "memory"); \ +}) + +#define prefetch_insn(base, fn) \ +({ \ + asm volatile( \ + "mv a0, %0\n" \ + "li a1, %1\n" \ + ".4byte %2\n" \ + : : "r" (base), "i" (fn), "i" (MK_PREFETCH(fn)) : "a0", "a1"); \ +}) + +static void cbo_inval(char *base) { cbo_insn(base, 0); } +static void cbo_clean(char *base) { cbo_insn(base, 1); } +static void cbo_flush(char *base) { cbo_insn(base, 2); } +static void cbo_zero(char *base) { cbo_insn(base, 4); } +static void prefetch_i(char *base) { prefetch_insn(base, 0); } +static void prefetch_r(char *base) { prefetch_insn(base, 1); } +static void prefetch_w(char *base) { prefetch_insn(base, 3); } + +static void test_no_cbo_inval(void *arg) +{ + ksft_print_msg("Testing cbo.inval instruction remain privileged\n"); + got_fault = false; + cbo_inval(&mem[0]); + ksft_test_result(got_fault, "No cbo.inval\n"); +} + +static void test_no_zicbom(void *arg) +{ + ksft_print_msg("Testing Zicbom instructions remain privileged\n"); + + got_fault = false; + cbo_clean(&mem[0]); + ksft_test_result(got_fault, "No cbo.clean\n"); + + got_fault = false; + cbo_flush(&mem[0]); + ksft_test_result(got_fault, "No cbo.flush\n"); +} + +static void test_no_zicboz(void *arg) +{ + ksft_print_msg("No Zicboz, testing cbo.zero remains privileged\n"); + + got_fault = false; + cbo_zero(&mem[0]); + ksft_test_result(got_fault, "No cbo.zero\n"); +} + +static bool is_power_of_2(__u64 n) +{ + return n != 0 && (n & (n - 1)) == 0; +} + +static void test_zicbop(void *arg) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE, + }; + struct sigaction act = { + .sa_sigaction = &fault_handler, + .sa_flags = SA_SIGINFO + }; + struct sigaction dfl = { + .sa_handler = SIG_DFL + }; + cpu_set_t *cpus = (cpu_set_t *)arg; + __u64 block_size; + long rc; + + rc = sigaction(SIGSEGV, &act, NULL); + assert(rc == 0); + rc = sigaction(SIGBUS, &act, NULL); + assert(rc == 0); + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)cpus, 0); + block_size = pair.value; + ksft_test_result(rc == 0 && pair.key == RISCV_HWPROBE_KEY_ZICBOP_BLOCK_SIZE && + is_power_of_2(block_size), "Zicbop block size\n"); + ksft_print_msg("Zicbop block size: %llu\n", block_size); + + got_fault = false; + prefetch_i(&mem[0]); + prefetch_r(&mem[0]); + prefetch_w(&mem[0]); + ksft_test_result(!got_fault, "Zicbop prefetch.* on valid address\n"); + + got_fault = false; + prefetch_i(NULL); + prefetch_r(NULL); + prefetch_w(NULL); + ksft_test_result(!got_fault, "Zicbop prefetch.* on NULL\n"); + + rc = sigaction(SIGBUS, &dfl, NULL); + assert(rc == 0); + rc = sigaction(SIGSEGV, &dfl, NULL); + assert(rc == 0); +} + +static void test_zicbom(void *arg) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE, + }; + cpu_set_t *cpus = (cpu_set_t *)arg; + __u64 block_size; + long rc; + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)cpus, 0); + block_size = pair.value; + ksft_test_result(rc == 0 && pair.key == RISCV_HWPROBE_KEY_ZICBOM_BLOCK_SIZE && + is_power_of_2(block_size), "Zicbom block size\n"); + ksft_print_msg("Zicbom block size: %llu\n", block_size); + + got_fault = false; + cbo_clean(&mem[block_size]); + ksft_test_result(!got_fault, "cbo.clean\n"); + + got_fault = false; + cbo_flush(&mem[block_size]); + ksft_test_result(!got_fault, "cbo.flush\n"); +} + +static void test_zicboz(void *arg) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE, + }; + cpu_set_t *cpus = (cpu_set_t *)arg; + __u64 block_size; + int i, j; + long rc; + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)cpus, 0); + block_size = pair.value; + ksft_test_result(rc == 0 && pair.key == RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE && + is_power_of_2(block_size), "Zicboz block size\n"); + ksft_print_msg("Zicboz block size: %llu\n", block_size); + + got_fault = false; + cbo_zero(&mem[block_size]); + ksft_test_result(!got_fault, "cbo.zero\n"); + + if (got_fault || !is_power_of_2(block_size)) { + ksft_test_result_skip("cbo.zero check\n"); + return; + } + + assert(block_size <= 1024); + + for (i = 0; i < 4096 / block_size; ++i) { + if (i % 2) + cbo_zero(&mem[i * block_size]); + } + + for (i = 0; i < 4096 / block_size; ++i) { + char expected = i % 2 ? 0x0 : 0xa5; + + for (j = 0; j < block_size; ++j) { + if (mem[i * block_size + j] != expected) { + ksft_test_result_fail("cbo.zero check\n"); + ksft_print_msg("cbo.zero check: mem[%llu] != 0x%x\n", + i * block_size + j, expected); + return; + } + } + } + + ksft_test_result_pass("cbo.zero check\n"); +} + +static void check_no_zicbo_cpus(cpu_set_t *cpus, __u64 cbo) +{ + struct riscv_hwprobe pair = { + .key = RISCV_HWPROBE_KEY_IMA_EXT_0, + }; + cpu_set_t one_cpu; + int i = 0, c = 0; + long rc; + char *cbostr; + + while (i++ < CPU_COUNT(cpus)) { + while (!CPU_ISSET(c, cpus)) + ++c; + + CPU_ZERO(&one_cpu); + CPU_SET(c, &one_cpu); + + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&one_cpu, 0); + assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0); + + switch (cbo) { + case RISCV_HWPROBE_EXT_ZICBOZ: + cbostr = "Zicboz"; + break; + case RISCV_HWPROBE_EXT_ZICBOM: + cbostr = "Zicbom"; + break; + case RISCV_HWPROBE_EXT_ZICBOP: + cbostr = "Zicbop"; + break; + default: + ksft_exit_fail_msg("Internal error: invalid cbo %llu\n", cbo); + } + + if (pair.value & cbo) + ksft_exit_fail_msg("%s is only present on a subset of harts.\n" + "Use taskset to select a set of harts where %s\n" + "presence (present or not) is consistent for each hart\n", + cbostr, cbostr); + ++c; + } +} + +enum { + TEST_ZICBOZ, + TEST_NO_ZICBOZ, + TEST_ZICBOM, + TEST_NO_ZICBOM, + TEST_NO_CBO_INVAL, + TEST_ZICBOP, +}; + +static struct test_info { + bool enabled; + unsigned int nr_tests; + void (*test_fn)(void *arg); +} tests[] = { + [TEST_ZICBOZ] = { .nr_tests = 3, test_zicboz }, + [TEST_NO_ZICBOZ] = { .nr_tests = 1, test_no_zicboz }, + [TEST_ZICBOM] = { .nr_tests = 3, test_zicbom }, + [TEST_NO_ZICBOM] = { .nr_tests = 2, test_no_zicbom }, + [TEST_NO_CBO_INVAL] = { .nr_tests = 1, test_no_cbo_inval }, + [TEST_ZICBOP] = { .nr_tests = 3, test_zicbop }, +}; + +static const struct option long_opts[] = { + {"zicbom-raises-sigill", no_argument, 0, 'm'}, + {"zicboz-raises-sigill", no_argument, 0, 'z'}, + {0, 0, 0, 0} +}; + +int main(int argc, char **argv) +{ + struct sigaction act = { + .sa_sigaction = &fault_handler, + .sa_flags = SA_SIGINFO, + }; + struct riscv_hwprobe pair; + unsigned int plan = 0; + cpu_set_t cpus; + long rc; + int i, opt, long_index; + + long_index = 0; + + while ((opt = getopt_long(argc, argv, "mz", long_opts, &long_index)) != -1) { + switch (opt) { + case 'm': + tests[TEST_NO_ZICBOM].enabled = true; + tests[TEST_NO_CBO_INVAL].enabled = true; + rc = sigaction(SIGILL, &act, NULL); + assert(rc == 0); + break; + case 'z': + tests[TEST_NO_ZICBOZ].enabled = true; + tests[TEST_NO_CBO_INVAL].enabled = true; + rc = sigaction(SIGILL, &act, NULL); + assert(rc == 0); + break; + case '?': + fprintf(stderr, + "Usage: %s [--zicbom-raises-sigill|-m] [--zicboz-raises-sigill|-z]\n", + argv[0]); + exit(1); + default: + break; + } + } + + rc = sched_getaffinity(0, sizeof(cpu_set_t), &cpus); + assert(rc == 0); + + ksft_print_header(); + + pair.key = RISCV_HWPROBE_KEY_IMA_EXT_0; + rc = riscv_hwprobe(&pair, 1, sizeof(cpu_set_t), (unsigned long *)&cpus, 0); + if (rc < 0) + ksft_exit_fail_msg("hwprobe() failed with %ld\n", rc); + assert(rc == 0 && pair.key == RISCV_HWPROBE_KEY_IMA_EXT_0); + + if (pair.value & RISCV_HWPROBE_EXT_ZICBOZ) { + tests[TEST_ZICBOZ].enabled = true; + tests[TEST_NO_ZICBOZ].enabled = false; + } else { + check_no_zicbo_cpus(&cpus, RISCV_HWPROBE_EXT_ZICBOZ); + } + + if (pair.value & RISCV_HWPROBE_EXT_ZICBOM) { + tests[TEST_ZICBOM].enabled = true; + tests[TEST_NO_ZICBOM].enabled = false; + } else { + check_no_zicbo_cpus(&cpus, RISCV_HWPROBE_EXT_ZICBOM); + } + + if (pair.value & RISCV_HWPROBE_EXT_ZICBOP) + tests[TEST_ZICBOP].enabled = true; + else + check_no_zicbo_cpus(&cpus, RISCV_HWPROBE_EXT_ZICBOP); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) + plan += tests[i].enabled ? tests[i].nr_tests : 0; + + if (plan == 0) + ksft_print_msg("No tests enabled.\n"); + else + ksft_set_plan(plan); + + for (i = 0; i < ARRAY_SIZE(tests); ++i) { + if (tests[i].enabled) + tests[i].test_fn(&cpus); + } + + ksft_finished(); +} diff --git a/tools/testing/selftests/riscv/hwprobe/hwprobe.c b/tools/testing/selftests/riscv/hwprobe/hwprobe.c new file mode 100644 index 000000000..eca4441ee --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/hwprobe.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "hwprobe.h" +#include "kselftest.h" + +int main(int argc, char **argv) +{ + struct riscv_hwprobe pairs[8]; + unsigned long cpus; + long out; + + ksft_print_header(); + ksft_set_plan(6); + + /* Fake the CPU_SET ops. */ + cpus = -1; + + /* + * Just run a basic test: pass enough pairs to get up to the base + * behavior, and then check to make sure it's sane. + */ + for (long i = 0; i < 8; i++) + pairs[i].key = i; + + out = riscv_hwprobe(pairs, 8, 1, &cpus, 0); + if (out != 0) + ksft_exit_fail_msg("hwprobe() failed with %ld\n", out); + + for (long i = 0; i < 4; ++i) { + /* Fail if the kernel claims not to recognize a base key. */ + if ((i < 4) && (pairs[i].key != i)) + ksft_exit_fail_msg("Failed to recognize base key: key != i, " + "key=%lld, i=%ld\n", pairs[i].key, i); + + if (pairs[i].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR) + continue; + + if (pairs[i].value & RISCV_HWPROBE_BASE_BEHAVIOR_IMA) + continue; + + ksft_exit_fail_msg("Unexpected pair: (%lld, %llu)\n", pairs[i].key, pairs[i].value); + } + + out = riscv_hwprobe(pairs, 8, 0, 0, 0); + ksft_test_result(out == 0, "NULL CPU set\n"); + + out = riscv_hwprobe(pairs, 8, 0, &cpus, 0); + ksft_test_result(out != 0, "Bad CPU set\n"); + + out = riscv_hwprobe(pairs, 8, 1, 0, 0); + ksft_test_result(out != 0, "NULL CPU set with non-zero size\n"); + + pairs[0].key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR; + out = riscv_hwprobe(pairs, 1, 1, &cpus, 0); + ksft_test_result(out == 0 && pairs[0].key == RISCV_HWPROBE_KEY_BASE_BEHAVIOR, + "Existing key is maintained\n"); + + pairs[0].key = 0x5555; + pairs[1].key = 1; + pairs[1].value = 0xAAAA; + out = riscv_hwprobe(pairs, 2, 0, 0, 0); + ksft_test_result(out == 0 && pairs[0].key == -1 && + pairs[1].key == 1 && pairs[1].value != 0xAAAA, + "Unknown key overwritten with -1 and doesn't block other elements\n"); + + pairs[0].key = RISCV_HWPROBE_KEY_IMA_EXT_0; + out = riscv_hwprobe(pairs, 1, 0, 0, 0); + if (out != 0) + ksft_exit_fail_msg("hwprobe(IMA_EXT_0) failed with %ld\n", out); + + /* + * The RISC-V ISA manual specifies that Zfh implies Zfhmin and Zvfh + * implies Zvfhmin, so hwprobe must report the implied subset + * extensions whenever the supersets are present. + */ + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZFHMIN)) + ksft_exit_fail_msg("Zfh reported without implied Zfhmin\n"); + if ((pairs[0].value & RISCV_HWPROBE_EXT_ZVFH) && + !(pairs[0].value & RISCV_HWPROBE_EXT_ZVFHMIN)) + ksft_exit_fail_msg("Zvfh reported without implied Zvfhmin\n"); + ksft_test_result_pass("Zfh/Zvfh imply Zfhmin/Zvfhmin\n"); + + ksft_finished(); +} diff --git a/tools/testing/selftests/riscv/hwprobe/hwprobe.h b/tools/testing/selftests/riscv/hwprobe/hwprobe.h new file mode 100644 index 000000000..f3de970c3 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/hwprobe.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef SELFTEST_RISCV_HWPROBE_H +#define SELFTEST_RISCV_HWPROBE_H +#include <stddef.h> +#include <asm/hwprobe.h> + +#if __BYTE_ORDER == __BIG_ENDIAN +# define le32_bswap(_x) \ + ((((_x) & 0x000000ffU) << 24) | \ + (((_x) & 0x0000ff00U) << 8) | \ + (((_x) & 0x00ff0000U) >> 8) | \ + (((_x) & 0xff000000U) >> 24)) +#else +# define le32_bswap(_x) (_x) +#endif + +/* + * Rather than relying on having a new enough libc to define this, just do it + * ourselves. This way we don't need to be coupled to a new-enough libc to + * contain the call. + */ +long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count, + size_t cpusetsize, unsigned long *cpus, unsigned int flags); + +#endif diff --git a/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S b/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S new file mode 100644 index 000000000..a4773c88d --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Rivos, Inc */ + +.text +.global riscv_hwprobe +riscv_hwprobe: + # Put __NR_riscv_hwprobe in the syscall number register, then just shim + # back the kernel's return. This doesn't do any sort of errno + # handling, the caller can deal with it. + li a7, 258 + ecall + ret diff --git a/tools/testing/selftests/riscv/hwprobe/which-cpus.c b/tools/testing/selftests/riscv/hwprobe/which-cpus.c new file mode 100644 index 000000000..587feb198 --- /dev/null +++ b/tools/testing/selftests/riscv/hwprobe/which-cpus.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Ventana Micro Systems Inc. + * + * Test the RISCV_HWPROBE_WHICH_CPUS flag of hwprobe. Also provides a command + * line interface to get the cpu list for arbitrary hwprobe pairs. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sched.h> +#include <unistd.h> +#include <assert.h> + +#include "hwprobe.h" +#include "kselftest.h" + +static void help(void) +{ + printf("\n" + "which-cpus: [-h] [<key=value> [<key=value> ...]]\n\n" + " Without parameters, tests the RISCV_HWPROBE_WHICH_CPUS flag of hwprobe.\n" + " With parameters, where each parameter is a hwprobe pair written as\n" + " <key=value>, outputs the cpulist for cpus which all match the given set\n" + " of pairs. 'key' and 'value' should be in numeric form, e.g. 4=0x3b\n"); +} + +static void print_cpulist(cpu_set_t *cpus) +{ + int start = 0, end = 0; + + if (!CPU_COUNT(cpus)) { + printf("cpus: None\n"); + return; + } + + printf("cpus:"); + for (int i = 0, c = 0; i < CPU_COUNT(cpus); i++, c++) { + if (start != end && !CPU_ISSET(c, cpus)) + printf("-%d", end); + + while (!CPU_ISSET(c, cpus)) + ++c; + + if (i != 0 && c == end + 1) { + end = c; + continue; + } + + printf("%c%d", i == 0 ? ' ' : ',', c); + start = end = c; + } + if (start != end) + printf("-%d", end); + printf("\n"); +} + +static void do_which_cpus(int argc, char **argv, cpu_set_t *cpus) +{ + struct riscv_hwprobe *pairs; + int nr_pairs = argc - 1; + char *start, *end; + int rc; + + pairs = malloc(nr_pairs * sizeof(struct riscv_hwprobe)); + assert(pairs); + + for (int i = 0; i < nr_pairs; i++) { + start = argv[i + 1]; + pairs[i].key = strtol(start, &end, 0); + assert(end != start && *end == '='); + start = end + 1; + pairs[i].value = strtoul(start, &end, 0); + assert(end != start && *end == '\0'); + } + + rc = riscv_hwprobe(pairs, nr_pairs, sizeof(cpu_set_t), (unsigned long *)cpus, RISCV_HWPROBE_WHICH_CPUS); + assert(rc == 0); + print_cpulist(cpus); + free(pairs); +} + +int main(int argc, char **argv) +{ + struct riscv_hwprobe pairs[3]; + cpu_set_t cpus_aff, cpus; + __u64 ext0_all, ext1_all; + long rc; + + rc = sched_getaffinity(0, sizeof(cpu_set_t), &cpus_aff); + assert(rc == 0); + + if (argc > 1) { + if (!strcmp(argv[1], "-h")) + help(); + else + do_which_cpus(argc, argv, &cpus_aff); + return 0; + } + + ksft_print_header(); + ksft_set_plan(7); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, }; + rc = riscv_hwprobe(pairs, 1, 0, NULL, 0); + assert(rc == 0 && pairs[0].key == RISCV_HWPROBE_KEY_BASE_BEHAVIOR && + pairs[0].value == RISCV_HWPROBE_BASE_BEHAVIOR_IMA); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_0, }; + rc = riscv_hwprobe(pairs, 1, 0, NULL, 0); + assert(rc == 0 && pairs[0].key == RISCV_HWPROBE_KEY_IMA_EXT_0); + ext0_all = pairs[0].value; + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_1, }; + rc = riscv_hwprobe(pairs, 1, 0, NULL, 0); + assert(rc == 0 && pairs[0].key == RISCV_HWPROBE_KEY_IMA_EXT_1); + ext1_all = pairs[0].value; + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + CPU_ZERO(&cpus); + rc = riscv_hwprobe(pairs, 1, 0, (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == -EINVAL, "no cpusetsize\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + rc = riscv_hwprobe(pairs, 1, sizeof(cpu_set_t), NULL, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == -EINVAL, "NULL cpus\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = 0xbadc0de, }; + CPU_ZERO(&cpus); + rc = riscv_hwprobe(pairs, 1, sizeof(cpu_set_t), (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == 0 && CPU_COUNT(&cpus) == 0, "unknown key\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + pairs[1] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + CPU_ZERO(&cpus); + rc = riscv_hwprobe(pairs, 2, sizeof(cpu_set_t), (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == 0, "duplicate keys\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + pairs[1] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_0, .value = ext0_all, }; + pairs[2] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_1, .value = ext1_all, }; + CPU_ZERO(&cpus); + rc = riscv_hwprobe(pairs, 3, sizeof(cpu_set_t), (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == 0 && CPU_COUNT(&cpus) == sysconf(_SC_NPROCESSORS_ONLN), "set all cpus\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + pairs[1] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_0, .value = ext0_all, }; + pairs[2] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_1, .value = ext1_all, }; + memcpy(&cpus, &cpus_aff, sizeof(cpu_set_t)); + rc = riscv_hwprobe(pairs, 3, sizeof(cpu_set_t), (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == 0 && CPU_EQUAL(&cpus, &cpus_aff), "set all affinity cpus\n"); + + pairs[0] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR, .value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA, }; + pairs[1] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_0, .value = ~ext0_all, }; + pairs[2] = (struct riscv_hwprobe){ .key = RISCV_HWPROBE_KEY_IMA_EXT_1, .value = ~ext1_all, }; + memcpy(&cpus, &cpus_aff, sizeof(cpu_set_t)); + rc = riscv_hwprobe(pairs, 3, sizeof(cpu_set_t), (unsigned long *)&cpus, RISCV_HWPROBE_WHICH_CPUS); + ksft_test_result(rc == 0 && CPU_COUNT(&cpus) == 0, "clear all cpus\n"); + + ksft_finished(); +} diff --git a/tools/testing/selftests/riscv/mm/.gitignore b/tools/testing/selftests/riscv/mm/.gitignore new file mode 100644 index 000000000..5c2c57cb9 --- /dev/null +++ b/tools/testing/selftests/riscv/mm/.gitignore @@ -0,0 +1,2 @@ +mmap_bottomup +mmap_default diff --git a/tools/testing/selftests/riscv/mm/Makefile b/tools/testing/selftests/riscv/mm/Makefile new file mode 100644 index 000000000..24122453e --- /dev/null +++ b/tools/testing/selftests/riscv/mm/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 ARM Limited +# Originally tools/testing/arm64/abi/Makefile + +# Additional include paths needed by kselftest.h and local headers +CFLAGS += -std=gnu99 -I. + +TEST_GEN_FILES := mmap_default mmap_bottomup + +TEST_PROGS := run_mmap.sh + +include ../../lib.mk + +$(OUTPUT)/mm: mmap_default.c mmap_bottomup.c mmap_tests.h + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/mm/mmap_bottomup.c b/tools/testing/selftests/riscv/mm/mmap_bottomup.c new file mode 100644 index 000000000..461a65c9b --- /dev/null +++ b/tools/testing/selftests/riscv/mm/mmap_bottomup.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/mman.h> +#include <mmap_test.h> + +#include "kselftest_harness.h" + +TEST(infinite_rlimit) +{ + EXPECT_EQ(BOTTOM_UP, memory_layout()); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/mm/mmap_default.c b/tools/testing/selftests/riscv/mm/mmap_default.c new file mode 100644 index 000000000..58db7d172 --- /dev/null +++ b/tools/testing/selftests/riscv/mm/mmap_default.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/mman.h> +#include <mmap_test.h> + +#include "kselftest_harness.h" + +TEST(default_rlimit) +{ + EXPECT_EQ(TOP_DOWN, memory_layout()); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/mm/mmap_test.h b/tools/testing/selftests/riscv/mm/mmap_test.h new file mode 100644 index 000000000..266a6becd --- /dev/null +++ b/tools/testing/selftests/riscv/mm/mmap_test.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _TESTCASES_MMAP_TEST_H +#define _TESTCASES_MMAP_TEST_H +#include <sys/mman.h> +#include <sys/resource.h> +#include <stddef.h> +#include <strings.h> +#include "kselftest_harness.h" + +#define TOP_DOWN 0 +#define BOTTOM_UP 1 + +#define PROT (PROT_READ | PROT_WRITE) +#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS) + +static inline int memory_layout(void) +{ + void *value1 = mmap(NULL, sizeof(int), PROT, FLAGS, 0, 0); + void *value2 = mmap(NULL, sizeof(int), PROT, FLAGS, 0, 0); + + return value2 > value1; +} +#endif /* _TESTCASES_MMAP_TEST_H */ diff --git a/tools/testing/selftests/riscv/mm/run_mmap.sh b/tools/testing/selftests/riscv/mm/run_mmap.sh new file mode 100755 index 000000000..ca5ad7c48 --- /dev/null +++ b/tools/testing/selftests/riscv/mm/run_mmap.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +original_stack_limit=$(ulimit -s) + +./mmap_default + +# Force mmap_bottomup to be ran with bottomup memory due to +# the unlimited stack +ulimit -s unlimited +./mmap_bottomup +ulimit -s $original_stack_limit diff --git a/tools/testing/selftests/riscv/sigreturn/.gitignore b/tools/testing/selftests/riscv/sigreturn/.gitignore new file mode 100644 index 000000000..35002b8ae --- /dev/null +++ b/tools/testing/selftests/riscv/sigreturn/.gitignore @@ -0,0 +1 @@ +sigreturn diff --git a/tools/testing/selftests/riscv/sigreturn/Makefile b/tools/testing/selftests/riscv/sigreturn/Makefile new file mode 100644 index 000000000..8c7750864 --- /dev/null +++ b/tools/testing/selftests/riscv/sigreturn/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 := sigreturn + +include ../../lib.mk + +$(OUTPUT)/sigreturn: sigreturn.c + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ diff --git a/tools/testing/selftests/riscv/sigreturn/sigreturn.c b/tools/testing/selftests/riscv/sigreturn/sigreturn.c new file mode 100644 index 000000000..e10873d95 --- /dev/null +++ b/tools/testing/selftests/riscv/sigreturn/sigreturn.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <ucontext.h> +#include <linux/ptrace.h> +#include "kselftest_harness.h" + +#define RISCV_V_MAGIC 0x53465457 +#define DEFAULT_VALUE 2 +#define SIGNAL_HANDLER_OVERRIDE 3 + +static void simple_handle(int sig_no, siginfo_t *info, void *vcontext) +{ + ucontext_t *context = vcontext; + + context->uc_mcontext.__gregs[REG_PC] = context->uc_mcontext.__gregs[REG_PC] + 4; +} + +static void vector_override(int sig_no, siginfo_t *info, void *vcontext) +{ + ucontext_t *context = vcontext; + + // vector state + struct __riscv_extra_ext_header *ext; + struct __riscv_v_ext_state *v_ext_state; + + /* Find the vector context. */ + ext = (void *)(&context->uc_mcontext.__fpregs); + if (ext->hdr.magic != RISCV_V_MAGIC) { + fprintf(stderr, "bad vector magic: %x\n", ext->hdr.magic); + abort(); + } + + v_ext_state = (void *)((char *)(ext) + sizeof(*ext)); + + *(int *)v_ext_state->datap = SIGNAL_HANDLER_OVERRIDE; + + context->uc_mcontext.__gregs[REG_PC] = context->uc_mcontext.__gregs[REG_PC] + 4; +} + +static int vector_sigreturn(int data, void (*handler)(int, siginfo_t *, void *)) +{ + int after_sigreturn; + struct sigaction sig_action = { + .sa_sigaction = handler, + .sa_flags = SA_SIGINFO + }; + + sigaction(SIGSEGV, &sig_action, 0); + + asm(".option push \n\ + .option arch, +v \n\ + vsetivli x0, 1, e32, m1, ta, ma \n\ + vmv.s.x v0, %1 \n\ + # Generate SIGSEGV \n\ + lw a0, 0(x0) \n\ + vmv.x.s %0, v0 \n\ + .option pop" : "=r" (after_sigreturn) : "r" (data)); + + return after_sigreturn; +} + +TEST(vector_restore) +{ + int result; + + result = vector_sigreturn(DEFAULT_VALUE, &simple_handle); + + EXPECT_EQ(DEFAULT_VALUE, result); +} + +TEST(vector_restore_signal_handler_override) +{ + int result; + + result = vector_sigreturn(DEFAULT_VALUE, &vector_override); + + EXPECT_EQ(SIGNAL_HANDLER_OVERRIDE, result); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/vector/.gitignore b/tools/testing/selftests/riscv/vector/.gitignore new file mode 100644 index 000000000..40a82baf3 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/.gitignore @@ -0,0 +1,6 @@ +vstate_exec_nolibc +vstate_prctl +v_initval +v_exec_initval_nolibc +vstate_ptrace +validate_v_ptrace diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile new file mode 100644 index 000000000..7e0017b3f --- /dev/null +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 ARM Limited +# Originally tools/testing/arm64/abi/Makefile + +TEST_GEN_PROGS := v_initval vstate_prctl vstate_ptrace validate_v_ptrace +TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc +TEST_GEN_LIBS := v_helpers.c sys_hwprobe.c + +include ../../lib.mk + +TEST_GEN_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(TEST_GEN_LIBS)) + +$(OUTPUT)/sys_hwprobe.o: ../hwprobe/sys_hwprobe.S + $(CC) -static -c -o $@ $(CFLAGS) $^ + +$(OUTPUT)/v_helpers.o: v_helpers.c + $(CC) -static -c -o $@ $(CFLAGS) $^ + +$(OUTPUT)/vstate_prctl: vstate_prctl.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/vstate_exec_nolibc: vstate_exec_nolibc.c + $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \ + -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc + +$(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/v_exec_initval_nolibc: v_exec_initval_nolibc.c + $(CC) -nostdlib -static -include ../../../../include/nolibc/nolibc.h \ + -Wall $(CFLAGS) $(LDFLAGS) $^ -o $@ -lgcc + +$(OUTPUT)/vstate_ptrace: vstate_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +$(OUTPUT)/validate_v_ptrace: validate_v_ptrace.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o + $(CC) -static -o $@ $(CFLAGS) $(LDFLAGS) $^ + +EXTRA_CLEAN += $(TEST_GEN_OBJ) diff --git a/tools/testing/selftests/riscv/vector/v_exec_initval_nolibc.c b/tools/testing/selftests/riscv/vector/v_exec_initval_nolibc.c new file mode 100644 index 000000000..4dde05e45 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/v_exec_initval_nolibc.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Get values of vector registers as soon as the program starts to test if + * is properly cleaning the values before starting a new program. Vector + * registers are caller saved, so no function calls may happen before reading + * the values. To further ensure consistency, this file is compiled without + * libc and without auto-vectorization. + * + * To be "clean" all values must be all zeroes. + */ + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +int main(int argc, char **argv) +{ + char value = 0; + unsigned long vl; + + if (argc > 2 && strcmp(argv[2], "x")) + asm volatile ( + // 0 | zimm[10:0] | rs1 | 1 1 1 | rd |1010111| vsetvli + // vsetvli t4, x0, e8, m1, d1 + ".4byte 0b00000000000000000111111011010111\n\t" + "mv %[vl], t4\n\t" + : [vl] "=r" (vl) : : "t4" + ); + else + asm volatile ( + ".option push\n\t" + ".option arch, +v\n\t" + "vsetvli %[vl], x0, e8, m1, ta, ma\n\t" + ".option pop\n\t" + : [vl] "=r" (vl) + ); + +#define CHECK_VECTOR_REGISTER(register) ({ \ + for (int i = 0; i < vl; i++) { \ + asm volatile ( \ + ".option push\n\t" \ + ".option arch, +v\n\t" \ + "vmv.x.s %0, " __stringify(register) "\n\t" \ + "vsrl.vi " __stringify(register) ", " __stringify(register) ", 8\n\t" \ + ".option pop\n\t" \ + : "=r" (value)); \ + if (value != 0x00) { \ + printf("Register " __stringify(register) \ + " values not clean! value: %u\n", value); \ + exit(-1); \ + } \ + } \ +}) + + CHECK_VECTOR_REGISTER(v0); + CHECK_VECTOR_REGISTER(v1); + CHECK_VECTOR_REGISTER(v2); + CHECK_VECTOR_REGISTER(v3); + CHECK_VECTOR_REGISTER(v4); + CHECK_VECTOR_REGISTER(v5); + CHECK_VECTOR_REGISTER(v6); + CHECK_VECTOR_REGISTER(v7); + CHECK_VECTOR_REGISTER(v8); + CHECK_VECTOR_REGISTER(v9); + CHECK_VECTOR_REGISTER(v10); + CHECK_VECTOR_REGISTER(v11); + CHECK_VECTOR_REGISTER(v12); + CHECK_VECTOR_REGISTER(v13); + CHECK_VECTOR_REGISTER(v14); + CHECK_VECTOR_REGISTER(v15); + CHECK_VECTOR_REGISTER(v16); + CHECK_VECTOR_REGISTER(v17); + CHECK_VECTOR_REGISTER(v18); + CHECK_VECTOR_REGISTER(v19); + CHECK_VECTOR_REGISTER(v20); + CHECK_VECTOR_REGISTER(v21); + CHECK_VECTOR_REGISTER(v22); + CHECK_VECTOR_REGISTER(v23); + CHECK_VECTOR_REGISTER(v24); + CHECK_VECTOR_REGISTER(v25); + CHECK_VECTOR_REGISTER(v26); + CHECK_VECTOR_REGISTER(v27); + CHECK_VECTOR_REGISTER(v28); + CHECK_VECTOR_REGISTER(v29); + CHECK_VECTOR_REGISTER(v30); + CHECK_VECTOR_REGISTER(v31); + +#undef CHECK_VECTOR_REGISTER + + return 0; +} diff --git a/tools/testing/selftests/riscv/vector/v_helpers.c b/tools/testing/selftests/riscv/vector/v_helpers.c new file mode 100644 index 000000000..de6da7c8d --- /dev/null +++ b/tools/testing/selftests/riscv/vector/v_helpers.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "../hwprobe/hwprobe.h" +#include <asm/vendor/thead.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/wait.h> + +bool is_xtheadvector_supported(void) +{ + struct riscv_hwprobe pair; + + pair.key = RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0; + riscv_hwprobe(&pair, 1, 0, NULL, 0); + return pair.value & RISCV_HWPROBE_VENDOR_EXT_XTHEADVECTOR; +} + +bool is_vector_supported(void) +{ + struct riscv_hwprobe pair; + + pair.key = RISCV_HWPROBE_KEY_IMA_EXT_0; + riscv_hwprobe(&pair, 1, 0, NULL, 0); + return pair.value & RISCV_HWPROBE_EXT_ZVE32X; +} + +unsigned long get_vr_len(void) +{ + unsigned long vlenb; + + if (is_vector_supported()) { + asm volatile("csrr %[vlenb], vlenb" : [vlenb] "=r"(vlenb)); + return vlenb; + } + + if (is_xtheadvector_supported()) { + asm volatile ( + // 0 | zimm[10:0] | rs1 | 1 1 1 | rd | 1010111 | vsetvli + // vsetvli t4, x0, e8, m1, d1 + ".4byte 0b00000000000000000111111011010111\n\t" + "mv %[vlenb], t4\n\t" + : [vlenb] "=r"(vlenb) : : "memory", "t4"); + return vlenb; + } + + printf("WARNING: vector not supported\n"); + return 0; +} + +int launch_test(char *next_program, int test_inherit, int xtheadvector) +{ + char *exec_argv[4], *exec_envp[1]; + int rc, pid, status; + + pid = fork(); + if (pid < 0) { + printf("fork failed %d", pid); + return -1; + } + + if (!pid) { + exec_argv[0] = next_program; + exec_argv[1] = test_inherit != 0 ? "x" : NULL; + exec_argv[2] = xtheadvector != 0 ? "x" : NULL; + exec_argv[3] = NULL; + exec_envp[0] = NULL; + /* launch the program again to check inherit */ + rc = execve(next_program, exec_argv, exec_envp); + if (rc) { + perror("execve"); + printf("child execve failed %d\n", rc); + exit(-1); + } + } + + rc = waitpid(-1, &status, 0); + if (rc < 0) { + printf("waitpid failed\n"); + return -3; + } + + if ((WIFEXITED(status) && WEXITSTATUS(status) == -1) || + WIFSIGNALED(status)) { + printf("child exited abnormally\n"); + return -4; + } + + return WEXITSTATUS(status); +} diff --git a/tools/testing/selftests/riscv/vector/v_helpers.h b/tools/testing/selftests/riscv/vector/v_helpers.h new file mode 100644 index 000000000..c538077f1 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/v_helpers.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <stdbool.h> + +bool is_xtheadvector_supported(void); + +bool is_vector_supported(void); + +unsigned long get_vr_len(void); + +int launch_test(char *next_program, int test_inherit, int xtheadvector); diff --git a/tools/testing/selftests/riscv/vector/v_initval.c b/tools/testing/selftests/riscv/vector/v_initval.c new file mode 100644 index 000000000..5fd2382e1 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/v_initval.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "kselftest_harness.h" +#include "v_helpers.h" + +#define NEXT_PROGRAM "./v_exec_initval_nolibc" + +TEST(v_initval) +{ + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + ASSERT_EQ(0, launch_test(NEXT_PROGRAM, 0, xtheadvector)); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c new file mode 100644 index 000000000..a388b7963 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/uio.h> +#include <unistd.h> +#include <errno.h> + +#include <linux/ptrace.h> +#include <linux/elf.h> + +#include "kselftest_harness.h" +#include "v_helpers.h" + +#define SR_FS_DIRTY 0x00006000UL +#define CSR_VXRM_SHIFT 1 + +volatile unsigned long chld_lock; + +TEST(ptrace_v_not_enabled) +{ + pid_t pid; + + if (!(is_vector_supported() || is_xtheadvector_supported())) + SKIP(return, "Vector not supported"); + + chld_lock = 1; + pid = (pid_t)syscall(SYS_clone, SIGCHLD, 0, NULL, 0, NULL); + ASSERT_LE(0, pid) + TH_LOG("clone: %m"); + + if (pid == 0) { + while (chld_lock == 1) + asm volatile("" : : "g"(chld_lock) : "memory"); + + asm volatile ("ebreak" : : : ); + } else { + struct __riscv_v_regset_state *regset_data; + unsigned long vlenb = get_vr_len(); + size_t regset_size; + struct iovec iov; + int status; + int ret; + + /* attach */ + + ASSERT_EQ(0, ptrace(PTRACE_ATTACH, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* unlock */ + + ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, pid, &chld_lock, 0)); + + /* resume and wait for ebreak */ + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* try to read vector registers from the tracee */ + + regset_size = sizeof(*regset_data) + vlenb * 32; + regset_data = calloc(1, regset_size); + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + /* V extension is available, but not yet enabled for the tracee */ + + errno = 0; + ret = ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov); + ASSERT_EQ(ENODATA, errno); + ASSERT_EQ(-1, ret); + + /* cleanup */ + free(regset_data); + ASSERT_EQ(0, kill(pid, SIGKILL)); + } +} + +TEST(ptrace_v_early_debug) +{ + static volatile unsigned long vstart; + static volatile unsigned long vtype; + static volatile unsigned long vlenb; + static volatile unsigned long vcsr; + static volatile unsigned long vl; + bool xtheadvector; + pid_t pid; + + if (!(is_vector_supported() || is_xtheadvector_supported())) + SKIP(return, "Vector not supported"); + + xtheadvector = is_xtheadvector_supported(); + + chld_lock = 1; + pid = fork(); + ASSERT_LE(0, pid) + TH_LOG("fork: %m"); + + if (pid == 0) { + unsigned long vxsat, vxrm; + + vlenb = get_vr_len(); + + while (chld_lock == 1) + asm volatile ("" : : "g"(chld_lock) : "memory"); + + asm volatile ( + "csrr %[vstart], vstart\n" + "csrr %[vtype], vtype\n" + "csrr %[vl], vl\n" + : [vtype] "=r"(vtype), [vstart] "=r"(vstart), [vl] "=r"(vl) + : + : "memory"); + + /* no 'is_xtheadvector_supported()' here to avoid clobbering v-state by syscall */ + if (xtheadvector) { + asm volatile ( + "csrs sstatus, %[bit]\n" + "csrr %[vxsat], vxsat\n" + "csrr %[vxrm], vxrm\n" + : [vxsat] "=r"(vxsat), [vxrm] "=r"(vxrm) + : [bit] "r" (SR_FS_DIRTY) + : "memory"); + vcsr = vxsat | vxrm << CSR_VXRM_SHIFT; + } else { + asm volatile ( + "csrr %[vcsr], vcsr\n" + : [vcsr] "=r"(vcsr) + : + : "memory"); + } + + asm volatile ( + ".option push\n" + ".option norvc\n" + "ebreak\n" + ".option pop\n"); + } else { + struct __riscv_v_regset_state *regset_data; + unsigned long vstart_csr; + unsigned long vlenb_csr; + unsigned long vtype_csr; + unsigned long vcsr_csr; + unsigned long vl_csr; + size_t regset_size; + struct iovec iov; + int status; + + /* attach */ + + ASSERT_EQ(0, ptrace(PTRACE_ATTACH, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* unlock */ + + ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, pid, &chld_lock, 0)); + + /* resume and wait for ebreak */ + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace PEEKDATA */ + + errno = 0; + vstart_csr = ptrace(PTRACE_PEEKDATA, pid, &vstart, NULL); + ASSERT_FALSE((errno != 0) && (vstart_csr == -1)); + + errno = 0; + vl_csr = ptrace(PTRACE_PEEKDATA, pid, &vl, NULL); + ASSERT_FALSE((errno != 0) && (vl_csr == -1)); + + errno = 0; + vtype_csr = ptrace(PTRACE_PEEKDATA, pid, &vtype, NULL); + ASSERT_FALSE((errno != 0) && (vtype_csr == -1)); + + errno = 0; + vcsr_csr = ptrace(PTRACE_PEEKDATA, pid, &vcsr, NULL); + ASSERT_FALSE((errno != 0) && (vcsr_csr == -1)); + + errno = 0; + vlenb_csr = ptrace(PTRACE_PEEKDATA, pid, &vlenb, NULL); + ASSERT_FALSE((errno != 0) && (vlenb_csr == -1)); + + /* read tracee csr regs using ptrace GETREGSET */ + + regset_size = sizeof(*regset_data) + vlenb_csr * 32; + regset_data = calloc(1, regset_size); + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* compare */ + + EXPECT_EQ(vstart_csr, regset_data->vstart); + EXPECT_EQ(vtype_csr, regset_data->vtype); + EXPECT_EQ(vlenb_csr, regset_data->vlenb); + EXPECT_EQ(vcsr_csr, regset_data->vcsr); + EXPECT_EQ(vl_csr, regset_data->vl); + + /* cleanup */ + free(regset_data); + ASSERT_EQ(0, kill(pid, SIGKILL)); + } +} + +TEST(ptrace_v_syscall_clobbering) +{ + pid_t pid; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + chld_lock = 1; + pid = fork(); + ASSERT_LE(0, pid) + TH_LOG("fork: %m"); + + if (pid == 0) { + unsigned long vl; + + while (chld_lock == 1) + asm volatile("" : : "g"(chld_lock) : "memory"); + + if (is_xtheadvector_supported()) { + asm volatile ( + // 0 | zimm[10:0] | rs1 | 1 1 1 | rd |1010111| vsetvli + // vsetvli t4, x0, e16, m2, d1 + ".4byte 0b00000000010100000111111011010111\n" + "mv %[new_vl], t4\n" + : [new_vl] "=r" (vl) : : "t4"); + } else { + asm volatile ( + ".option push\n" + ".option arch, +zve32x\n" + "vsetvli %[new_vl], x0, e16, m2, tu, mu\n" + ".option pop\n" + : [new_vl] "=r"(vl) : : ); + } + + while (1) { + asm volatile ( + ".option push\n" + ".option norvc\n" + "ebreak\n" + ".option pop\n"); + + sleep(0); + } + } else { + struct __riscv_v_regset_state *regset_data; + unsigned long vlenb = get_vr_len(); + struct user_regs_struct regs; + size_t regset_size; + struct iovec iov; + int status; + + /* attach */ + + ASSERT_EQ(0, ptrace(PTRACE_ATTACH, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* unlock */ + + ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, pid, &chld_lock, 0)); + + /* resume and wait for the 1st ebreak */ + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace GETREGSET */ + + regset_size = sizeof(*regset_data) + vlenb * 32; + regset_data = calloc(1, regset_size); + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify initial vsetvli settings */ + + if (is_xtheadvector_supported()) { + EXPECT_EQ(5UL, regset_data->vtype); + } else { + EXPECT_EQ(9UL, regset_data->vtype); + } + + EXPECT_EQ(regset_data->vlenb, regset_data->vl); + EXPECT_EQ(vlenb, regset_data->vlenb); + EXPECT_EQ(0UL, regset_data->vstart); + EXPECT_EQ(0UL, regset_data->vcsr); + + /* skip 1st ebreak, then resume and wait for the 2nd ebreak */ + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); + regs.pc += 4; + ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vtype using ptrace GETREGSET */ + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify that V state is illegal after syscall */ + + EXPECT_EQ((1UL << (__riscv_xlen - 1)), regset_data->vtype); + EXPECT_EQ(vlenb, regset_data->vlenb); + EXPECT_EQ(0UL, regset_data->vstart); + EXPECT_EQ(0UL, regset_data->vcsr); + EXPECT_EQ(0UL, regset_data->vl); + + /* cleanup */ + free(regset_data); + ASSERT_EQ(0, kill(pid, SIGKILL)); + } +} + +FIXTURE(v_csr_invalid) +{ +}; + +FIXTURE_SETUP(v_csr_invalid) +{ +} + +FIXTURE_TEARDOWN(v_csr_invalid) +{ +} + +#define VECTOR_1_0 _BITUL(0) +#define XTHEAD_VECTOR_0_7 _BITUL(1) + +#define vector_test(x) ((x) & VECTOR_1_0) +#define xthead_test(x) ((x) & XTHEAD_VECTOR_0_7) + +/* modifications of the initial vsetvli settings */ +FIXTURE_VARIANT(v_csr_invalid) +{ + unsigned long vstart; + unsigned long vl; + unsigned long vtype; + unsigned long vcsr; + unsigned long vlenb_mul; + unsigned long vlenb_min; + unsigned long vlenb_max; + unsigned long spec; +}; + +/* unexpected vlenb value */ +FIXTURE_VARIANT_ADD(v_csr_invalid, new_vlenb) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x3, + .vcsr = 0x0, + .vlenb_mul = 0x2, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0 | XTHEAD_VECTOR_0_7, +}; + +/* invalid reserved bits in vcsr */ +FIXTURE_VARIANT_ADD(v_csr_invalid, vcsr_invalid_reserved_bits) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x3, + .vcsr = 0x1UL << 8, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0 | XTHEAD_VECTOR_0_7, +}; + +/* invalid reserved bits in vtype */ +FIXTURE_VARIANT_ADD(v_csr_invalid, vtype_invalid_reserved_bits) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = (0x1UL << 8) | 0x3, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0 | XTHEAD_VECTOR_0_7, +}; + +/* set vill bit */ +FIXTURE_VARIANT_ADD(v_csr_invalid, invalid_vill_bit) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = (0x1UL << (__riscv_xlen - 1)) | 0x3, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0 | XTHEAD_VECTOR_0_7, +}; + +/* reserved vsew value: vsew > 3 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, reserved_vsew) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x4UL << 3, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0, +}; + +/* XTheadVector: unsupported non-zero VEDIV value */ +FIXTURE_VARIANT_ADD(v_csr_invalid, reserved_vediv) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x3UL << 5, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = XTHEAD_VECTOR_0_7, +}; + +/* reserved vlmul value: vlmul == 4 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, reserved_vlmul) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x4, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x0, + .spec = VECTOR_1_0, +}; + +/* invalid fractional LMUL for VLEN <= 256: LMUL= 1/8, SEW = 64 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, frac_lmul1) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x1d, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x20, + .spec = VECTOR_1_0, +}; + +/* invalid integral LMUL for VLEN <= 16: LMUL= 2, SEW = 64 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, int_lmul1) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x19, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x2, + .spec = VECTOR_1_0, +}; + +/* XTheadVector: invalid integral LMUL for VLEN <= 16: LMUL= 2, SEW = 64 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, int_lmul2) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0xd, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x2, + .spec = XTHEAD_VECTOR_0_7, +}; + +/* invalid VL for VLEN <= 128: LMUL= 2, SEW = 64, VL = 8 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, vl1) +{ + .vstart = 0x0, + .vl = 0x8, + .vtype = 0x19, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x10, + .spec = VECTOR_1_0, +}; + +/* XTheadVector: invalid VL for VLEN <= 128: LMUL= 2, SEW = 64, VL = 8 */ +FIXTURE_VARIANT_ADD(v_csr_invalid, vl2) +{ + .vstart = 0x0, + .vl = 0x8, + .vtype = 0xd, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x0, + .vlenb_max = 0x10, + .spec = XTHEAD_VECTOR_0_7, +}; + +TEST_F(v_csr_invalid, ptrace_v_invalid_values) +{ + unsigned long vlenb; + pid_t pid; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vectors not supported"); + + if (is_vector_supported() && !vector_test(variant->spec)) + SKIP(return, "Test not supported for Vector"); + + if (is_xtheadvector_supported() && !xthead_test(variant->spec)) + SKIP(return, "Test not supported for XTheadVector"); + + vlenb = get_vr_len(); + + if (variant->vlenb_min) { + if (vlenb < variant->vlenb_min) + SKIP(return, "This test does not support VLEN < %lu\n", + variant->vlenb_min * 8); + } + + if (variant->vlenb_max) { + if (vlenb > variant->vlenb_max) + SKIP(return, "This test does not support VLEN > %lu\n", + variant->vlenb_max * 8); + } + + chld_lock = 1; + pid = fork(); + ASSERT_LE(0, pid) + TH_LOG("fork: %m"); + + if (pid == 0) { + unsigned long vl; + + while (chld_lock == 1) + asm volatile("" : : "g"(chld_lock) : "memory"); + + if (is_xtheadvector_supported()) { + asm volatile ( + // 0 | zimm[10:0] | rs1 | 1 1 1 | rd |1010111| vsetvli + // vsetvli t4, x0, e16, m2, d1 + ".4byte 0b00000000010100000111111011010111\n" + "mv %[new_vl], t4\n" + : [new_vl] "=r" (vl) : : "t4"); + } else { + asm volatile ( + ".option push\n" + ".option arch, +zve32x\n" + "vsetvli %[new_vl], x0, e16, m2, tu, mu\n" + ".option pop\n" + : [new_vl] "=r"(vl) : : ); + } + + while (1) { + asm volatile ( + ".option push\n" + ".option norvc\n" + "ebreak\n" + "nop\n" + ".option pop\n"); + } + } else { + struct __riscv_v_regset_state *regset_data; + size_t regset_size; + struct iovec iov; + int status; + int ret; + + /* attach */ + + ASSERT_EQ(0, ptrace(PTRACE_ATTACH, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* unlock */ + + ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, pid, &chld_lock, 0)); + + /* resume and wait for the 1st ebreak */ + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace GETREGSET */ + + regset_size = sizeof(*regset_data) + vlenb * 32; + regset_data = calloc(1, regset_size); + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify initial vsetvli settings */ + + if (is_xtheadvector_supported()) { + EXPECT_EQ(5UL, regset_data->vtype); + } else { + EXPECT_EQ(9UL, regset_data->vtype); + } + + EXPECT_EQ(regset_data->vlenb, regset_data->vl); + EXPECT_EQ(vlenb, regset_data->vlenb); + EXPECT_EQ(0UL, regset_data->vstart); + EXPECT_EQ(0UL, regset_data->vcsr); + + /* apply invalid settings from fixture variants */ + + regset_data->vlenb *= variant->vlenb_mul; + regset_data->vstart = variant->vstart; + regset_data->vtype = variant->vtype; + regset_data->vcsr = variant->vcsr; + regset_data->vl = variant->vl; + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + errno = 0; + ret = ptrace(PTRACE_SETREGSET, pid, NT_RISCV_VECTOR, &iov); + ASSERT_EQ(errno, EINVAL); + ASSERT_EQ(ret, -1); + + /* cleanup */ + free(regset_data); + ASSERT_EQ(0, kill(pid, SIGKILL)); + } +} + +FIXTURE(v_csr_valid) +{ +}; + +FIXTURE_SETUP(v_csr_valid) +{ +} + +FIXTURE_TEARDOWN(v_csr_valid) +{ +} + +/* modifications of the initial vsetvli settings */ +FIXTURE_VARIANT(v_csr_valid) +{ + unsigned long vstart; + unsigned long vl; + unsigned long vtype; + unsigned long vcsr; + unsigned long vlenb_mul; + unsigned long vlenb_min; + unsigned long vlenb_max; + unsigned long spec; +}; + +/* valid for VLEN >= 128: LMUL= 1/4, SEW = 32 */ +FIXTURE_VARIANT_ADD(v_csr_valid, frac_lmul1) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x16, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x10, + .vlenb_max = 0x0, + .spec = VECTOR_1_0, +}; + +/* valid for VLEN >= 16: LMUL= 2, SEW = 32 */ +FIXTURE_VARIANT_ADD(v_csr_valid, int_lmul1) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x11, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x2, + .vlenb_max = 0x0, + .spec = VECTOR_1_0, +}; + +/* valid for XTheadVector VLEN >= 16: LMUL= 2, SEW = 32 */ +FIXTURE_VARIANT_ADD(v_csr_valid, int_lmul2) +{ + .vstart = 0x0, + .vl = 0x0, + .vtype = 0x9, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x2, + .vlenb_max = 0x0, + .spec = XTHEAD_VECTOR_0_7, +}; + +/* valid for VLEN >= 32: LMUL= 2, SEW = 32, VL = 2 */ +FIXTURE_VARIANT_ADD(v_csr_valid, int_lmul3) +{ + .vstart = 0x0, + .vl = 0x2, + .vtype = 0x11, + .vcsr = 0x0, + .vlenb_mul = 0x1, + .vlenb_min = 0x4, + .vlenb_max = 0x0, + .spec = VECTOR_1_0, +}; + +TEST_F(v_csr_valid, ptrace_v_valid_values) +{ + unsigned long vlenb; + pid_t pid; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vectors not supported"); + + if (is_vector_supported() && !vector_test(variant->spec)) + SKIP(return, "Test not supported for Vector"); + + if (is_xtheadvector_supported() && !xthead_test(variant->spec)) + SKIP(return, "Test not supported for XTheadVector"); + + vlenb = get_vr_len(); + + if (variant->vlenb_min) { + if (vlenb < variant->vlenb_min) + SKIP(return, "This test does not support VLEN < %lu\n", + variant->vlenb_min * 8); + } + if (variant->vlenb_max) { + if (vlenb > variant->vlenb_max) + SKIP(return, "This test does not support VLEN > %lu\n", + variant->vlenb_max * 8); + } + + chld_lock = 1; + pid = fork(); + ASSERT_LE(0, pid) + TH_LOG("fork: %m"); + + if (pid == 0) { + unsigned long vl; + + while (chld_lock == 1) + asm volatile("" : : "g"(chld_lock) : "memory"); + + if (is_xtheadvector_supported()) { + asm volatile ( + // 0 | zimm[10:0] | rs1 | 1 1 1 | rd |1010111| vsetvli + // vsetvli t4, x0, e16, m2, d1 + ".4byte 0b00000000010100000111111011010111\n" + "mv %[new_vl], t4\n" + : [new_vl] "=r" (vl) : : "t4"); + } else { + asm volatile ( + ".option push\n" + ".option arch, +zve32x\n" + "vsetvli %[new_vl], x0, e16, m2, tu, mu\n" + ".option pop\n" + : [new_vl] "=r"(vl) : : ); + } + + asm volatile ( + ".option push\n" + ".option norvc\n" + ".option arch, +zve32x\n" + "ebreak\n" /* breakpoint 1: apply new V state using ptrace */ + "nop\n" + "ebreak\n" /* breakpoint 2: V state clean - context will not be saved */ + "vmv.v.i v0, -1\n" + "ebreak\n" /* breakpoint 3: V state dirty - context will be saved */ + ".option pop\n"); + } else { + struct __riscv_v_regset_state *regset_data; + struct user_regs_struct regs; + size_t regset_size; + struct iovec iov; + int status; + + /* attach */ + + ASSERT_EQ(0, ptrace(PTRACE_ATTACH, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* unlock */ + + ASSERT_EQ(0, ptrace(PTRACE_POKEDATA, pid, &chld_lock, 0)); + + /* resume and wait for the 1st ebreak */ + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace GETREGSET */ + + regset_size = sizeof(*regset_data) + vlenb * 32; + regset_data = calloc(1, regset_size); + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify initial vsetvli settings */ + + if (is_xtheadvector_supported()) { + EXPECT_EQ(5UL, regset_data->vtype); + } else { + EXPECT_EQ(9UL, regset_data->vtype); + } + + EXPECT_EQ(regset_data->vlenb, regset_data->vl); + EXPECT_EQ(vlenb, regset_data->vlenb); + EXPECT_EQ(0UL, regset_data->vstart); + EXPECT_EQ(0UL, regset_data->vcsr); + + /* apply valid settings from fixture variants */ + + regset_data->vlenb *= variant->vlenb_mul; + regset_data->vstart = variant->vstart; + regset_data->vtype = variant->vtype; + regset_data->vcsr = variant->vcsr; + regset_data->vl = variant->vl; + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* skip 1st ebreak, then resume and wait for the 2nd ebreak */ + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); + regs.pc += 4; + ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace GETREGSET */ + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify vector csr regs from tracee context */ + + EXPECT_EQ(regset_data->vstart, variant->vstart); + EXPECT_EQ(regset_data->vtype, variant->vtype); + EXPECT_EQ(regset_data->vcsr, variant->vcsr); + EXPECT_EQ(regset_data->vl, variant->vl); + EXPECT_EQ(regset_data->vlenb, vlenb); + + /* skip 2nd ebreak, then resume and wait for the 3rd ebreak */ + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); + regs.pc += 4; + ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); + + ASSERT_EQ(0, ptrace(PTRACE_CONT, pid, NULL, NULL)); + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFSTOPPED(status)); + + /* read tracee vector csr regs using ptrace GETREGSET */ + + iov.iov_base = regset_data; + iov.iov_len = regset_size; + + ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid, NT_RISCV_VECTOR, &iov)); + + /* verify vector csr regs from tracee context */ + + EXPECT_EQ(regset_data->vstart, variant->vstart); + EXPECT_EQ(regset_data->vtype, variant->vtype); + EXPECT_EQ(regset_data->vcsr, variant->vcsr); + EXPECT_EQ(regset_data->vl, variant->vl); + EXPECT_EQ(regset_data->vlenb, vlenb); + + /* cleanup */ + free(regset_data); + ASSERT_EQ(0, kill(pid, SIGKILL)); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c b/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c new file mode 100644 index 000000000..12f1b1b1c --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_exec_nolibc.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/wait.h> + +#define THIS_PROGRAM "./vstate_exec_nolibc" + +int main(int argc, char **argv) +{ + int rc, pid, status, test_inherit = 0, xtheadvector = 0; + long ctrl, ctrl_c; + char *exec_argv[2], *exec_envp[2]; + + if (argc > 1 && strcmp(argv[1], "x")) + test_inherit = 1; + + if (argc > 2 && strcmp(argv[2], "x")) + xtheadvector = 1; + + ctrl = prctl(PR_RISCV_V_GET_CONTROL, 0, 0, 0, 0); + if (ctrl == -1) { + puts("PR_RISCV_V_GET_CONTROL is not supported\n"); + exit(-1); + } + + if (test_inherit) { + pid = fork(); + if (pid == -1) { + puts("fork failed\n"); + exit(-1); + } + + /* child */ + if (!pid) { + exec_argv[0] = THIS_PROGRAM; + exec_argv[1] = NULL; + exec_envp[0] = NULL; + exec_envp[1] = NULL; + /* launch the program again to check inherit */ + rc = execve(THIS_PROGRAM, exec_argv, exec_envp); + if (rc) { + puts("child execve failed\n"); + exit(-1); + } + } + + } else { + pid = fork(); + if (pid == -1) { + puts("fork failed\n"); + exit(-1); + } + + if (!pid) { + rc = prctl(PR_RISCV_V_GET_CONTROL, 0, 0, 0, 0); + if (rc != ctrl) { + puts("child's vstate_ctrl not equal to parent's\n"); + exit(-1); + } + if (xtheadvector) + asm volatile (".4byte 0x00007ed7"); + else + asm volatile (".option push\n\t" + ".option arch, +v\n\t" + "vsetvli x0, x0, e32, m8, ta, ma\n\t" + ".option pop\n\t" + ); + exit(ctrl); + } + } + + rc = waitpid(-1, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == -1) { + puts("child exited abnormally\n"); + exit(-1); + } + + if (WIFSIGNALED(status)) { + if (WTERMSIG(status) != SIGILL) { + puts("child was terminated by unexpected signal\n"); + exit(-1); + } + + if ((ctrl & PR_RISCV_V_VSTATE_CTRL_CUR_MASK) != PR_RISCV_V_VSTATE_CTRL_OFF) { + puts("child signaled by illegal V access but vstate_ctrl is not off\n"); + exit(-1); + } + + /* child terminated, and its vstate_ctrl is off */ + exit(ctrl); + } + + ctrl_c = WEXITSTATUS(status); + if (test_inherit) { + if (ctrl & PR_RISCV_V_VSTATE_CTRL_INHERIT) { + if (!(ctrl_c & PR_RISCV_V_VSTATE_CTRL_INHERIT)) { + puts("parent has inherit bit, but child has not\n"); + exit(-1); + } + } + rc = (ctrl & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) >> 2; + if (rc != PR_RISCV_V_VSTATE_CTRL_DEFAULT) { + if (rc != (ctrl_c & PR_RISCV_V_VSTATE_CTRL_CUR_MASK)) { + puts("parent's next setting does not equal to child's\n"); + exit(-1); + } + + if (!(ctrl & PR_RISCV_V_VSTATE_CTRL_INHERIT)) { + if ((ctrl_c & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) != + PR_RISCV_V_VSTATE_CTRL_DEFAULT) { + puts("must clear child's next vstate_ctrl if !inherit\n"); + exit(-1); + } + } + } + } + return ctrl; +} diff --git a/tools/testing/selftests/riscv/vector/vstate_prctl.c b/tools/testing/selftests/riscv/vector/vstate_prctl.c new file mode 100644 index 000000000..d607af390 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_prctl.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <sys/prctl.h> +#include <unistd.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <stdlib.h> + +#include "kselftest_harness.h" +#include "v_helpers.h" + +#define NEXT_PROGRAM "./vstate_exec_nolibc" + +int test_and_compare_child(long provided, long expected, int inherit, int xtheadvector) +{ + int rc; + + rc = prctl(PR_RISCV_V_SET_CONTROL, provided); + if (rc != 0) { + printf("prctl with provided arg %lx failed with code %d\n", + provided, rc); + return -1; + } + rc = launch_test(NEXT_PROGRAM, inherit, xtheadvector); + if (rc != expected) { + printf("Test failed, check %d != %ld\n", rc, expected); + return -2; + } + return 0; +} + +#define PR_RISCV_V_VSTATE_CTRL_CUR_SHIFT 0 +#define PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT 2 + +TEST(get_control_no_v) +{ + long rc; + + if (is_vector_supported() || is_xtheadvector_supported()) + SKIP(return, "Test expects vector to be not supported"); + + rc = prctl(PR_RISCV_V_GET_CONTROL); + EXPECT_EQ(-1, rc) + TH_LOG("GET_CONTROL should fail on kernel/hw without ZVE32X"); + EXPECT_EQ(EINVAL, errno) + TH_LOG("GET_CONTROL should fail on kernel/hw without ZVE32X"); +} + +TEST(set_control_no_v) +{ + long rc; + + if (is_vector_supported() || is_xtheadvector_supported()) + SKIP(return, "Test expects vector to be not supported"); + + rc = prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_ON); + EXPECT_EQ(-1, rc) + TH_LOG("SET_CONTROL should fail on kernel/hw without ZVE32X"); + EXPECT_EQ(EINVAL, errno) + TH_LOG("SET_CONTROL should fail on kernel/hw without ZVE32X"); +} + +TEST(vstate_on_current) +{ + long flag; + long rc; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + flag = PR_RISCV_V_VSTATE_CTRL_ON; + rc = prctl(PR_RISCV_V_SET_CONTROL, flag); + EXPECT_EQ(0, rc) TH_LOG("Enabling V for current should always succeed"); +} + +TEST(vstate_off_eperm) +{ + long flag; + long rc; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + flag = PR_RISCV_V_VSTATE_CTRL_OFF; + rc = prctl(PR_RISCV_V_SET_CONTROL, flag); + EXPECT_EQ(EPERM, errno) + TH_LOG("Disabling V in current thread with V enabled must fail with EPERM(%d)", errno); + EXPECT_EQ(-1, rc) + TH_LOG("Disabling V in current thread with V enabled must fail with EPERM(%d)", errno); +} + +TEST(vstate_on_no_nesting) +{ + long flag; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + /* Turn on next's vector explicitly and test */ + flag = PR_RISCV_V_VSTATE_CTRL_ON << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + + EXPECT_EQ(0, test_and_compare_child(flag, PR_RISCV_V_VSTATE_CTRL_ON, 0, xtheadvector)); +} + +TEST(vstate_off_nesting) +{ + long flag; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + /* Turn off next's vector explicitly and test */ + flag = PR_RISCV_V_VSTATE_CTRL_OFF << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + + EXPECT_EQ(0, test_and_compare_child(flag, PR_RISCV_V_VSTATE_CTRL_OFF, 1, xtheadvector)); +} + +TEST(vstate_on_inherit_no_nesting) +{ + long flag, expected; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + /* Turn on next's vector explicitly and test no inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_ON << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_ON; + + EXPECT_EQ(0, test_and_compare_child(flag, expected, 0, xtheadvector)); +} + +TEST(vstate_on_inherit) +{ + long flag, expected; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + /* Turn on next's vector explicitly and test inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_ON << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_ON; + + EXPECT_EQ(0, test_and_compare_child(flag, expected, 1, xtheadvector)); +} + +TEST(vstate_off_inherit_no_nesting) +{ + long flag, expected; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + /* Turn off next's vector explicitly and test no inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_OFF << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_OFF; + + EXPECT_EQ(0, test_and_compare_child(flag, expected, 0, xtheadvector)); +} + +TEST(vstate_off_inherit) +{ + long flag, expected; + int xtheadvector = 0; + + if (!is_vector_supported()) { + if (is_xtheadvector_supported()) + xtheadvector = 1; + else + SKIP(return, "Vector not supported"); + } + + /* Turn off next's vector explicitly and test inherit */ + flag = PR_RISCV_V_VSTATE_CTRL_OFF << PR_RISCV_V_VSTATE_CTRL_NEXT_SHIFT; + flag |= PR_RISCV_V_VSTATE_CTRL_INHERIT; + expected = flag | PR_RISCV_V_VSTATE_CTRL_OFF; + + EXPECT_EQ(0, test_and_compare_child(flag, expected, 1, xtheadvector)); +} + +/* arguments should fail with EINVAL */ +TEST(inval_set_control_1) +{ + int rc; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0xff0); + EXPECT_EQ(-1, rc); + EXPECT_EQ(EINVAL, errno); +} + +/* arguments should fail with EINVAL */ +TEST(inval_set_control_2) +{ + int rc; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0x3); + EXPECT_EQ(-1, rc); + EXPECT_EQ(EINVAL, errno); +} + +/* arguments should fail with EINVAL */ +TEST(inval_set_control_3) +{ + int rc; + + if (!is_vector_supported() && !is_xtheadvector_supported()) + SKIP(return, "Vector not supported"); + + rc = prctl(PR_RISCV_V_SET_CONTROL, 0xc); + EXPECT_EQ(-1, rc); + EXPECT_EQ(EINVAL, errno); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/riscv/vector/vstate_ptrace.c b/tools/testing/selftests/riscv/vector/vstate_ptrace.c new file mode 100644 index 000000000..1479abc0c --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <stdio.h> +#include <stdlib.h> +#include <asm/ptrace.h> +#include <linux/elf.h> +#include <sys/ptrace.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include "../../kselftest.h" +#include "v_helpers.h" + +int parent_set_val, child_set_val; + +static long do_ptrace(enum __ptrace_request op, pid_t pid, long type, size_t size, void *data) +{ + struct iovec v_iovec = { + .iov_len = size, + .iov_base = data + }; + + return ptrace(op, pid, type, &v_iovec); +} + +static int do_child(void) +{ + int out; + + if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) { + ksft_perror("PTRACE_TRACEME failed\n"); + return EXIT_FAILURE; + } + + asm volatile (".option push\n\t" + ".option arch, +v\n\t" + ".option norvc\n\t" + "vsetivli x0, 1, e32, m1, ta, ma\n\t" + "vmv.s.x v31, %[in]\n\t" + "ebreak\n\t" + "vmv.x.s %[out], v31\n\t" + ".option pop\n\t" + : [out] "=r" (out) + : [in] "r" (child_set_val)); + + if (out != parent_set_val) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void do_parent(pid_t child) +{ + int status; + void *data = NULL; + + /* Attach to the child */ + while (waitpid(child, &status, 0)) { + if (WIFEXITED(status)) { + ksft_test_result(WEXITSTATUS(status) == 0, "SETREGSET vector\n"); + goto out; + } else if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGTRAP)) { + size_t size; + void *data, *v31; + struct __riscv_v_regset_state *v_regset_hdr; + struct user_regs_struct *gpreg; + + size = sizeof(*v_regset_hdr); + data = malloc(size); + if (!data) + goto out; + v_regset_hdr = (struct __riscv_v_regset_state *)data; + + if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data)) + goto out; + + ksft_print_msg("vlenb %ld\n", v_regset_hdr->vlenb); + data = realloc(data, size + v_regset_hdr->vlenb * 32); + if (!data) + goto out; + v_regset_hdr = (struct __riscv_v_regset_state *)data; + v31 = (void *)(data + size + v_regset_hdr->vlenb * 31); + size += v_regset_hdr->vlenb * 32; + + if (do_ptrace(PTRACE_GETREGSET, child, NT_RISCV_VECTOR, size, data)) + goto out; + + ksft_test_result(*(int *)v31 == child_set_val, "GETREGSET vector\n"); + + *(int *)v31 = parent_set_val; + if (do_ptrace(PTRACE_SETREGSET, child, NT_RISCV_VECTOR, size, data)) + goto out; + + /* move the pc forward */ + size = sizeof(*gpreg); + data = realloc(data, size); + gpreg = (struct user_regs_struct *)data; + + if (do_ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, size, data)) + goto out; + + gpreg->pc += 4; + if (do_ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, size, data)) + goto out; + } + + ptrace(PTRACE_CONT, child, NULL, NULL); + } + +out: + free(data); +} + +int main(void) +{ + pid_t child; + + ksft_set_plan(2); + if (!is_vector_supported() && !is_xtheadvector_supported()) + ksft_exit_skip("Vector not supported\n"); + + srandom(getpid()); + parent_set_val = rand(); + child_set_val = rand(); + + child = fork(); + if (child < 0) + ksft_exit_fail_msg("Fork failed %d\n", child); + + if (!child) + return do_child(); + + do_parent(child); + + ksft_finished(); +} |
