Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -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
@@ -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
@@ -0,0 +1 @@
pointer_masking
@@ -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) $^
@@ -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();
}
@@ -0,0 +1,2 @@
cfitests
shadowstack
@@ -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
@@ -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 __ASSEMBLY__
#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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
hwprobe
cbo
which-cpus
@@ -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) $^
@@ -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();
}
@@ -0,0 +1,66 @@
// 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(5);
/* 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");
ksft_finished();
}
@@ -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
@@ -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
@@ -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();
}
@@ -0,0 +1,2 @@
mmap_bottomup
mmap_default
@@ -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) $^
@@ -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
@@ -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
@@ -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 */
+12
View File
@@ -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
@@ -0,0 +1 @@
sigreturn
@@ -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) $^
@@ -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
@@ -0,0 +1,6 @@
vstate_exec_nolibc
vstate_prctl
v_initval
v_exec_initval_nolibc
vstate_ptrace
validate_v_ptrace
@@ -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)
@@ -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;
}
@@ -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);
}
@@ -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);
@@ -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
@@ -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 = &regs;
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 = &regs;
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 = &regs;
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
@@ -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;
}
@@ -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
@@ -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();
}