Add a test case that does some basic verification of the Vector ptrace interface. This forks a child process then using ptrace to inspect and manipulate the v31 register of the child.
Signed-off-by: Yong-Xuan Wang yongxuan.wang@sifive.com --- tools/testing/selftests/riscv/vector/Makefile | 5 +- .../selftests/riscv/vector/vstate_ptrace.c | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c
diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile index 6f7497f4e7b3..45f25e9dd264 100644 --- a/tools/testing/selftests/riscv/vector/Makefile +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -2,7 +2,7 @@ # Copyright (C) 2021 ARM Limited # Originally tools/testing/arm64/abi/Makefile
-TEST_GEN_PROGS := v_initval vstate_prctl +TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
include ../../lib.mk @@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o $(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) $^ 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 000000000000..8a7bcf318e59 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c @@ -0,0 +1,132 @@ +// 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" + "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, t; + 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; + 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 += 2; + 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(); +}
Hi Yong-Xuan,
On Wed, Oct 1, 2025 at 6:15 AM Yong-Xuan Wang yongxuan.wang@sifive.com wrote:
Add a test case that does some basic verification of the Vector ptrace interface. This forks a child process then using ptrace to inspect and manipulate the v31 register of the child.
Signed-off-by: Yong-Xuan Wang yongxuan.wang@sifive.com
tools/testing/selftests/riscv/vector/Makefile | 5 +- .../selftests/riscv/vector/vstate_ptrace.c | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c
diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile index 6f7497f4e7b3..45f25e9dd264 100644 --- a/tools/testing/selftests/riscv/vector/Makefile +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -2,7 +2,7 @@ # Copyright (C) 2021 ARM Limited # Originally tools/testing/arm64/abi/Makefile
-TEST_GEN_PROGS := v_initval vstate_prctl +TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
include ../../lib.mk @@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o $(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) $^
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 000000000000..8a7bcf318e59 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c @@ -0,0 +1,132 @@ +// 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"
"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, t;
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;
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 += 2;
Just nitpicking here, simply adding 2 may fail if the program is not compiled with C. You may either +=4 and use ".option norvc" in the asm or determine the size of ebreak by decoding it.
with or without the fix,
Reviewed-by: Andy Chiu andybnac@gmail.com
Hi Yong-Xuan,
I found some issues which deserve a re-roll:
On Wed, Oct 1, 2025 at 6:15 AM Yong-Xuan Wang yongxuan.wang@sifive.com wrote:
Add a test case that does some basic verification of the Vector ptrace interface. This forks a child process then using ptrace to inspect and manipulate the v31 register of the child.
Signed-off-by: Yong-Xuan Wang yongxuan.wang@sifive.com
tools/testing/selftests/riscv/vector/Makefile | 5 +- .../selftests/riscv/vector/vstate_ptrace.c | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c
diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile index 6f7497f4e7b3..45f25e9dd264 100644 --- a/tools/testing/selftests/riscv/vector/Makefile +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -2,7 +2,7 @@ # Copyright (C) 2021 ARM Limited # Originally tools/testing/arm64/abi/Makefile
-TEST_GEN_PROGS := v_initval vstate_prctl +TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace
Please s/vsate_ptrace/vstate_ptrace
Otherwise we will not get the program compiled
TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
include ../../lib.mk @@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o $(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) $^
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 000000000000..8a7bcf318e59 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c @@ -0,0 +1,132 @@ +// 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"
As mentioned before, please use ".option arch, +v,-c\n\t" or ".option norvc\n\t" and +=4 when advancing the pc
"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, t;
unused variable t
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);
realloc may give a new pointer so v_regset_hdr has to be updated here before the next use
if (!data)
goto out;
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 += 2;
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();
+}
2.43.0
Thanks, Andy
On Thu, Oct 02, 2025 at 12:53:13AM -0500, Andy Chiu wrote:
Hi Yong-Xuan,
I found some issues which deserve a re-roll:
On Wed, Oct 1, 2025 at 6:15 AM Yong-Xuan Wang yongxuan.wang@sifive.com wrote:
Add a test case that does some basic verification of the Vector ptrace interface. This forks a child process then using ptrace to inspect and manipulate the v31 register of the child.
Signed-off-by: Yong-Xuan Wang yongxuan.wang@sifive.com
tools/testing/selftests/riscv/vector/Makefile | 5 +- .../selftests/riscv/vector/vstate_ptrace.c | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/riscv/vector/vstate_ptrace.c
diff --git a/tools/testing/selftests/riscv/vector/Makefile b/tools/testing/selftests/riscv/vector/Makefile index 6f7497f4e7b3..45f25e9dd264 100644 --- a/tools/testing/selftests/riscv/vector/Makefile +++ b/tools/testing/selftests/riscv/vector/Makefile @@ -2,7 +2,7 @@ # Copyright (C) 2021 ARM Limited # Originally tools/testing/arm64/abi/Makefile
-TEST_GEN_PROGS := v_initval vstate_prctl +TEST_GEN_PROGS := v_initval vstate_prctl vsate_ptrace
Please s/vsate_ptrace/vstate_ptrace
Otherwise we will not get the program compiled
TEST_GEN_PROGS_EXTENDED := vstate_exec_nolibc v_exec_initval_nolibc
include ../../lib.mk @@ -26,3 +26,6 @@ $(OUTPUT)/v_initval: v_initval.c $(OUTPUT)/sys_hwprobe.o $(OUTPUT)/v_helpers.o $(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) $^
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 000000000000..8a7bcf318e59 --- /dev/null +++ b/tools/testing/selftests/riscv/vector/vstate_ptrace.c @@ -0,0 +1,132 @@ +// 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"
As mentioned before, please use ".option arch, +v,-c\n\t" or ".option norvc\n\t" and +=4 when advancing the pc
arch -c should be avoided, there are cases when it does not always avoid using all compressed instructions. norvc should always do the right thing though. There is discussion at [1] about deprecating it (along with all variants of -ext).
[1] https://inbox.sourceware.org/binutils/7ecdc846-0822-4666-957f-ff818786fb44@i...
- Charlie
"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, t;
unused variable t
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);
realloc may give a new pointer so v_regset_hdr has to be updated here before the next use
if (!data)
goto out;
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 += 2;
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();
+}
2.43.0
Thanks, Andy
linux-kselftest-mirror@lists.linaro.org