Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Mark Brown 3433 100.00% 15 100.00%
Total 3433 15


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2015-2021 ARM Limited.
 * Original author: Dave Martin <Dave.Martin@arm.com>
 */
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <asm/sigcontext.h>
#include <asm/ptrace.h>

#include "../../kselftest.h"

/* <linux/elf.h> and <sys/auxv.h> don't like each other, so: */
#ifndef NT_ARM_SVE
#define NT_ARM_SVE 0x405
#endif

#ifndef NT_ARM_SSVE
#define NT_ARM_SSVE 0x40b
#endif

/*
 * The architecture defines the maximum VQ as 16 but for extensibility
 * the kernel specifies the SVE_VQ_MAX as 512 resulting in us running
 * a *lot* more tests than are useful if we use it.  Until the
 * architecture is extended let's limit our coverage to what is
 * currently allowed, plus one extra to ensure we cover constraining
 * the VL as expected.
 */
#define TEST_VQ_MAX 17

struct vec_type {
	const char *name;
	unsigned long hwcap_type;
	unsigned long hwcap;
	int regset;
	int prctl_set;
};

static const struct vec_type vec_types[] = {
	{
		.name = "SVE",
		.hwcap_type = AT_HWCAP,
		.hwcap = HWCAP_SVE,
		.regset = NT_ARM_SVE,
		.prctl_set = PR_SVE_SET_VL,
	},
	{
		.name = "Streaming SVE",
		.hwcap_type = AT_HWCAP2,
		.hwcap = HWCAP2_SME,
		.regset = NT_ARM_SSVE,
		.prctl_set = PR_SME_SET_VL,
	},
};

#define VL_TESTS (((TEST_VQ_MAX - SVE_VQ_MIN) + 1) * 4)
#define FLAG_TESTS 2
#define FPSIMD_TESTS 2

#define EXPECTED_TESTS ((VL_TESTS + FLAG_TESTS + FPSIMD_TESTS) * ARRAY_SIZE(vec_types))

static void fill_buf(char *buf, size_t size)
{
	int i;

	for (i = 0; i < size; i++)
		buf[i] = random();
}

static int do_child(void)
{
	if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
		ksft_exit_fail_msg("PTRACE_TRACEME", strerror(errno));

	if (raise(SIGSTOP))
		ksft_exit_fail_msg("raise(SIGSTOP)", strerror(errno));

	return EXIT_SUCCESS;
}

static int get_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
{
	struct iovec iov;

	iov.iov_base = fpsimd;
	iov.iov_len = sizeof(*fpsimd);
	return ptrace(PTRACE_GETREGSET, pid, NT_PRFPREG, &iov);
}

static int set_fpsimd(pid_t pid, struct user_fpsimd_state *fpsimd)
{
	struct iovec iov;

	iov.iov_base = fpsimd;
	iov.iov_len = sizeof(*fpsimd);
	return ptrace(PTRACE_SETREGSET, pid, NT_PRFPREG, &iov);
}

static struct user_sve_header *get_sve(pid_t pid, const struct vec_type *type,
				       void **buf, size_t *size)
{
	struct user_sve_header *sve;
	void *p;
	size_t sz = sizeof *sve;
	struct iovec iov;

	while (1) {
		if (*size < sz) {
			p = realloc(*buf, sz);
			if (!p) {
				errno = ENOMEM;
				goto error;
			}

			*buf = p;
			*size = sz;
		}

		iov.iov_base = *buf;
		iov.iov_len = sz;
		if (ptrace(PTRACE_GETREGSET, pid, type->regset, &iov))
			goto error;

		sve = *buf;
		if (sve->size <= sz)
			break;

		sz = sve->size;
	}

	return sve;

error:
	return NULL;
}

static int set_sve(pid_t pid, const struct vec_type *type,
		   const struct user_sve_header *sve)
{
	struct iovec iov;

	iov.iov_base = (void *)sve;
	iov.iov_len = sve->size;
	return ptrace(PTRACE_SETREGSET, pid, type->regset, &iov);
}

/* Validate setting and getting the inherit flag */
static void ptrace_set_get_inherit(pid_t child, const struct vec_type *type)
{
	struct user_sve_header sve;
	struct user_sve_header *new_sve = NULL;
	size_t new_sve_size = 0;
	int ret;

	/* First set the flag */
	memset(&sve, 0, sizeof(sve));
	sve.size = sizeof(sve);
	sve.vl = sve_vl_from_vq(SVE_VQ_MIN);
	sve.flags = SVE_PT_VL_INHERIT;
	ret = set_sve(child, type, &sve);
	if (ret != 0) {
		ksft_test_result_fail("Failed to set %s SVE_PT_VL_INHERIT\n",
				      type->name);
		return;
	}

	/*
	 * Read back the new register state and verify that we have
	 * set the flags we expected.
	 */
	if (!get_sve(child, type, (void **)&new_sve, &new_sve_size)) {
		ksft_test_result_fail("Failed to read %s SVE flags\n",
				      type->name);
		return;
	}

	ksft_test_result(new_sve->flags & SVE_PT_VL_INHERIT,
			 "%s SVE_PT_VL_INHERIT set\n", type->name);

	/* Now clear */
	sve.flags &= ~SVE_PT_VL_INHERIT;
	ret = set_sve(child, type, &sve);
	if (ret != 0) {
		ksft_test_result_fail("Failed to clear %s SVE_PT_VL_INHERIT\n",
				      type->name);
		return;
	}

	if (!get_sve(child, type, (void **)&new_sve, &new_sve_size)) {
		ksft_test_result_fail("Failed to read %s SVE flags\n",
				      type->name);
		return;
	}

	ksft_test_result(!(new_sve->flags & SVE_PT_VL_INHERIT),
			 "%s SVE_PT_VL_INHERIT cleared\n", type->name);

	free(new_sve);
}

/* Validate attempting to set the specfied VL via ptrace */
static void ptrace_set_get_vl(pid_t child, const struct vec_type *type,
			      unsigned int vl, bool *supported)
{
	struct user_sve_header sve;
	struct user_sve_header *new_sve = NULL;
	size_t new_sve_size = 0;
	int ret, prctl_vl;

	*supported = false;

	/* Check if the VL is supported in this process */
	prctl_vl = prctl(type->prctl_set, vl);
	if (prctl_vl == -1)
		ksft_exit_fail_msg("prctl(PR_%s_SET_VL) failed: %s (%d)\n",
				   type->name, strerror(errno), errno);

	/* If the VL is not supported then a supported VL will be returned */
	*supported = (prctl_vl == vl);

	/* Set the VL by doing a set with no register payload */
	memset(&sve, 0, sizeof(sve));
	sve.size = sizeof(sve);
	sve.vl = vl;
	ret = set_sve(child, type, &sve);
	if (ret != 0) {
		ksft_test_result_fail("Failed to set %s VL %u\n",
				      type->name, vl);
		return;
	}

	/*
	 * Read back the new register state and verify that we have the
	 * same VL that we got from prctl() on ourselves.
	 */
	if (!get_sve(child, type, (void **)&new_sve, &new_sve_size)) {
		ksft_test_result_fail("Failed to read %s VL %u\n",
				      type->name, vl);
		return;
	}

	ksft_test_result(new_sve->vl = prctl_vl, "Set %s VL %u\n",
			 type->name, vl);

	free(new_sve);
}

static void check_u32(unsigned int vl, const char *reg,
		      uint32_t *in, uint32_t *out, int *errors)
{
	if (*in != *out) {
		printf("# VL %d %s wrote %x read %x\n",
		       vl, reg, *in, *out);
		(*errors)++;
	}
}

/* Access the FPSIMD registers via the SVE regset */
static void ptrace_sve_fpsimd(pid_t child, const struct vec_type *type)
{
	void *svebuf;
	struct user_sve_header *sve;
	struct user_fpsimd_state *fpsimd, new_fpsimd;
	unsigned int i, j;
	unsigned char *p;
	int ret;

	svebuf = malloc(SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD));
	if (!svebuf) {
		ksft_test_result_fail("Failed to allocate FPSIMD buffer\n");
		return;
	}

	memset(svebuf, 0, SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD));
	sve = svebuf;
	sve->flags = SVE_PT_REGS_FPSIMD;
	sve->size = SVE_PT_SIZE(0, SVE_PT_REGS_FPSIMD);
	sve->vl = 16;  /* We don't care what the VL is */

	/* Try to set a known FPSIMD state via PT_REGS_SVE */
	fpsimd = (struct user_fpsimd_state *)((char *)sve +
					      SVE_PT_FPSIMD_OFFSET);
	for (i = 0; i < 32; ++i) {
		p = (unsigned char *)&fpsimd->vregs[i];

		for (j = 0; j < sizeof(fpsimd->vregs[i]); ++j)
			p[j] = j;
	}

	ret = set_sve(child, type, sve);
	ksft_test_result(ret == 0, "%s FPSIMD set via SVE: %d\n",
			 type->name, ret);
	if (ret)
		goto out;

	/* Verify via the FPSIMD regset */
	if (get_fpsimd(child, &new_fpsimd)) {
		ksft_test_result_fail("get_fpsimd(): %s\n",
				      strerror(errno));
		goto out;
	}
	if (memcmp(fpsimd, &new_fpsimd, sizeof(*fpsimd)) == 0)
		ksft_test_result_pass("%s get_fpsimd() gave same state\n",
				      type->name);
	else
		ksft_test_result_fail("%s get_fpsimd() gave different state\n",
				      type->name);

out:
	free(svebuf);
}

/* Validate attempting to set SVE data and read SVE data */
static void ptrace_set_sve_get_sve_data(pid_t child,
					const struct vec_type *type,
					unsigned int vl)
{
	void *write_buf;
	void *read_buf = NULL;
	struct user_sve_header *write_sve;
	struct user_sve_header *read_sve;
	size_t read_sve_size = 0;
	unsigned int vq = sve_vq_from_vl(vl);
	int ret, i;
	size_t data_size;
	int errors = 0;

	data_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
	write_buf = malloc(data_size);
	if (!write_buf) {
		ksft_test_result_fail("Error allocating %d byte buffer for %s VL %u\n",
				      data_size, type->name, vl);
		return;
	}
	write_sve = write_buf;

	/* Set up some data and write it out */
	memset(write_sve, 0, data_size);
	write_sve->size = data_size;
	write_sve->vl = vl;
	write_sve->flags = SVE_PT_REGS_SVE;

	for (i = 0; i < __SVE_NUM_ZREGS; i++)
		fill_buf(write_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
			 SVE_PT_SVE_ZREG_SIZE(vq));

	for (i = 0; i < __SVE_NUM_PREGS; i++)
		fill_buf(write_buf + SVE_PT_SVE_PREG_OFFSET(vq, i),
			 SVE_PT_SVE_PREG_SIZE(vq));

	fill_buf(write_buf + SVE_PT_SVE_FPSR_OFFSET(vq), SVE_PT_SVE_FPSR_SIZE);
	fill_buf(write_buf + SVE_PT_SVE_FPCR_OFFSET(vq), SVE_PT_SVE_FPCR_SIZE);

	/* TODO: Generate a valid FFR pattern */

	ret = set_sve(child, type, write_sve);
	if (ret != 0) {
		ksft_test_result_fail("Failed to set %s VL %u data\n",
				      type->name, vl);
		goto out;
	}

	/* Read the data back */
	if (!get_sve(child, type, (void **)&read_buf, &read_sve_size)) {
		ksft_test_result_fail("Failed to read %s VL %u data\n",
				      type->name, vl);
		goto out;
	}
	read_sve = read_buf;

	/* We might read more data if there's extensions we don't know */
	if (read_sve->size < write_sve->size) {
		ksft_test_result_fail("%s wrote %d bytes, only read %d\n",
				      type->name, write_sve->size,
				      read_sve->size);
		goto out_read;
	}

	for (i = 0; i < __SVE_NUM_ZREGS; i++) {
		if (memcmp(write_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
			   read_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
			   SVE_PT_SVE_ZREG_SIZE(vq)) != 0) {
			printf("# Mismatch in %u Z%d\n", vl, i);
			errors++;
		}
	}

	for (i = 0; i < __SVE_NUM_PREGS; i++) {
		if (memcmp(write_buf + SVE_PT_SVE_PREG_OFFSET(vq, i),
			   read_buf + SVE_PT_SVE_PREG_OFFSET(vq, i),
			   SVE_PT_SVE_PREG_SIZE(vq)) != 0) {
			printf("# Mismatch in %u P%d\n", vl, i);
			errors++;
		}
	}

	check_u32(vl, "FPSR", write_buf + SVE_PT_SVE_FPSR_OFFSET(vq),
		  read_buf + SVE_PT_SVE_FPSR_OFFSET(vq), &errors);
	check_u32(vl, "FPCR", write_buf + SVE_PT_SVE_FPCR_OFFSET(vq),
		  read_buf + SVE_PT_SVE_FPCR_OFFSET(vq), &errors);

	ksft_test_result(errors == 0, "Set and get %s data for VL %u\n",
			 type->name, vl);

out_read:
	free(read_buf);
out:
	free(write_buf);
}

/* Validate attempting to set SVE data and read it via the FPSIMD regset */
static void ptrace_set_sve_get_fpsimd_data(pid_t child,
					   const struct vec_type *type,
					   unsigned int vl)
{
	void *write_buf;
	struct user_sve_header *write_sve;
	unsigned int vq = sve_vq_from_vl(vl);
	struct user_fpsimd_state fpsimd_state;
	int ret, i;
	size_t data_size;
	int errors = 0;

	if (__BYTE_ORDER == __BIG_ENDIAN) {
		ksft_test_result_skip("Big endian not supported\n");
		return;
	}

	data_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
	write_buf = malloc(data_size);
	if (!write_buf) {
		ksft_test_result_fail("Error allocating %d byte buffer for %s VL %u\n",
				      data_size, type->name, vl);
		return;
	}
	write_sve = write_buf;

	/* Set up some data and write it out */
	memset(write_sve, 0, data_size);
	write_sve->size = data_size;
	write_sve->vl = vl;
	write_sve->flags = SVE_PT_REGS_SVE;

	for (i = 0; i < __SVE_NUM_ZREGS; i++)
		fill_buf(write_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
			 SVE_PT_SVE_ZREG_SIZE(vq));

	fill_buf(write_buf + SVE_PT_SVE_FPSR_OFFSET(vq), SVE_PT_SVE_FPSR_SIZE);
	fill_buf(write_buf + SVE_PT_SVE_FPCR_OFFSET(vq), SVE_PT_SVE_FPCR_SIZE);

	ret = set_sve(child, type, write_sve);
	if (ret != 0) {
		ksft_test_result_fail("Failed to set %s VL %u data\n",
				      type->name, vl);
		goto out;
	}

	/* Read the data back */
	if (get_fpsimd(child, &fpsimd_state)) {
		ksft_test_result_fail("Failed to read %s VL %u FPSIMD data\n",
				      type->name, vl);
		goto out;
	}

	for (i = 0; i < __SVE_NUM_ZREGS; i++) {
		__uint128_t tmp = 0;

		/*
		 * Z regs are stored endianness invariant, this won't
		 * work for big endian
		 */
		memcpy(&tmp, write_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
		       sizeof(tmp));

		if (tmp != fpsimd_state.vregs[i]) {
			printf("# Mismatch in FPSIMD for %s VL %u Z%d\n",
			       type->name, vl, i);
			errors++;
		}
	}

	check_u32(vl, "FPSR", write_buf + SVE_PT_SVE_FPSR_OFFSET(vq),
		  &fpsimd_state.fpsr, &errors);
	check_u32(vl, "FPCR", write_buf + SVE_PT_SVE_FPCR_OFFSET(vq),
		  &fpsimd_state.fpcr, &errors);

	ksft_test_result(errors == 0, "Set and get FPSIMD data for %s VL %u\n",
			 type->name, vl);

out:
	free(write_buf);
}

/* Validate attempting to set FPSIMD data and read it via the SVE regset */
static void ptrace_set_fpsimd_get_sve_data(pid_t child,
					   const struct vec_type *type,
					   unsigned int vl)
{
	void *read_buf = NULL;
	unsigned char *p;
	struct user_sve_header *read_sve;
	unsigned int vq = sve_vq_from_vl(vl);
	struct user_fpsimd_state write_fpsimd;
	int ret, i, j;
	size_t read_sve_size = 0;
	size_t expected_size;
	int errors = 0;

	if (__BYTE_ORDER == __BIG_ENDIAN) {
		ksft_test_result_skip("Big endian not supported\n");
		return;
	}

	for (i = 0; i < 32; ++i) {
		p = (unsigned char *)&write_fpsimd.vregs[i];

		for (j = 0; j < sizeof(write_fpsimd.vregs[i]); ++j)
			p[j] = j;
	}

	ret = set_fpsimd(child, &write_fpsimd);
	if (ret != 0) {
		ksft_test_result_fail("Failed to set FPSIMD state: %d\n)",
				      ret);
		return;
	}

	if (!get_sve(child, type, (void **)&read_buf, &read_sve_size)) {
		ksft_test_result_fail("Failed to read %s VL %u data\n",
				      type->name, vl);
		return;
	}
	read_sve = read_buf;

	if (read_sve->vl != vl) {
		ksft_test_result_fail("Child VL != expected VL %d\n",
				      read_sve->vl, vl);
		goto out;
	}

	/* The kernel may return either SVE or FPSIMD format */
	switch (read_sve->flags & SVE_PT_REGS_MASK) {
	case SVE_PT_REGS_FPSIMD:
		expected_size = SVE_PT_FPSIMD_SIZE(vq, SVE_PT_REGS_FPSIMD);
		if (read_sve_size < expected_size) {
			ksft_test_result_fail("Read %d bytes, expected %d\n",
					      read_sve_size, expected_size);
			goto out;
		}

		ret = memcmp(&write_fpsimd, read_buf + SVE_PT_FPSIMD_OFFSET,
			     sizeof(write_fpsimd));
		if (ret != 0) {
			ksft_print_msg("Read FPSIMD data mismatch\n");
			errors++;
		}
		break;

	case SVE_PT_REGS_SVE:
		expected_size = SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE);
		if (read_sve_size < expected_size) {
			ksft_test_result_fail("Read %d bytes, expected %d\n",
					      read_sve_size, expected_size);
			goto out;
		}

		for (i = 0; i < __SVE_NUM_ZREGS; i++) {
			__uint128_t tmp = 0;

			/*
			 * Z regs are stored endianness invariant, this won't
			 * work for big endian
			 */
			memcpy(&tmp, read_buf + SVE_PT_SVE_ZREG_OFFSET(vq, i),
			       sizeof(tmp));

			if (tmp != write_fpsimd.vregs[i]) {
				ksft_print_msg("Mismatch in FPSIMD for %s VL %u Z%d/V%d\n",
					       type->name, vl, i, i);
				errors++;
			}
		}

		check_u32(vl, "FPSR", &write_fpsimd.fpsr,
			  read_buf + SVE_PT_SVE_FPSR_OFFSET(vq), &errors);
		check_u32(vl, "FPCR", &write_fpsimd.fpcr,
			  read_buf + SVE_PT_SVE_FPCR_OFFSET(vq), &errors);
		break;
	default:
		ksft_print_msg("Unexpected regs type %d\n",
			       read_sve->flags & SVE_PT_REGS_MASK);
		errors++;
		break;
	}

	ksft_test_result(errors == 0, "Set FPSIMD, read via SVE for %s VL %u\n",
			 type->name, vl);

out:
	free(read_buf);
}

static int do_parent(pid_t child)
{
	int ret = EXIT_FAILURE;
	pid_t pid;
	int status, i;
	siginfo_t si;
	unsigned int vq, vl;
	bool vl_supported;

	ksft_print_msg("Parent is %d, child is %d\n", getpid(), child);

	/* Attach to the child */
	while (1) {
		int sig;

		pid = wait(&status);
		if (pid == -1) {
			perror("wait");
			goto error;
		}

		/*
		 * This should never happen but it's hard to flag in
		 * the framework.
		 */
		if (pid != child)
			continue;

		if (WIFEXITED(status) || WIFSIGNALED(status))
			ksft_exit_fail_msg("Child died unexpectedly\n");

		if (!WIFSTOPPED(status))
			goto error;

		sig = WSTOPSIG(status);

		if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
			if (errno == ESRCH)
				goto disappeared;

			if (errno == EINVAL) {
				sig = 0; /* bust group-stop */
				goto cont;
			}

			ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n",
					      strerror(errno));
			goto error;
		}

		if (sig == SIGSTOP && si.si_code == SI_TKILL &&
		    si.si_pid == pid)
			break;

	cont:
		if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
			if (errno == ESRCH)
				goto disappeared;

			ksft_test_result_fail("PTRACE_CONT: %s\n",
					      strerror(errno));
			goto error;
		}
	}

	for (i = 0; i < ARRAY_SIZE(vec_types); i++) {
		/* FPSIMD via SVE regset */
		if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
			ptrace_sve_fpsimd(child, &vec_types[i]);
		} else {
			ksft_test_result_skip("%s FPSIMD set via SVE\n",
					      vec_types[i].name);
			ksft_test_result_skip("%s FPSIMD read\n",
					      vec_types[i].name);
		}

		/* prctl() flags */
		if (getauxval(vec_types[i].hwcap_type) & vec_types[i].hwcap) {
			ptrace_set_get_inherit(child, &vec_types[i]);
		} else {
			ksft_test_result_skip("%s SVE_PT_VL_INHERIT set\n",
					      vec_types[i].name);
			ksft_test_result_skip("%s SVE_PT_VL_INHERIT cleared\n",
					      vec_types[i].name);
		}

		/* Step through every possible VQ */
		for (vq = SVE_VQ_MIN; vq <= TEST_VQ_MAX; vq++) {
			vl = sve_vl_from_vq(vq);

			/* First, try to set this vector length */
			if (getauxval(vec_types[i].hwcap_type) &
			    vec_types[i].hwcap) {
				ptrace_set_get_vl(child, &vec_types[i], vl,
						  &vl_supported);
			} else {
				ksft_test_result_skip("%s get/set VL %d\n",
						      vec_types[i].name, vl);
				vl_supported = false;
			}

			/* If the VL is supported validate data set/get */
			if (vl_supported) {
				ptrace_set_sve_get_sve_data(child, &vec_types[i], vl);
				ptrace_set_sve_get_fpsimd_data(child, &vec_types[i], vl);
				ptrace_set_fpsimd_get_sve_data(child, &vec_types[i], vl);
			} else {
				ksft_test_result_skip("%s set SVE get SVE for VL %d\n",
						      vec_types[i].name, vl);
				ksft_test_result_skip("%s set SVE get FPSIMD for VL %d\n",
						      vec_types[i].name, vl);
				ksft_test_result_skip("%s set FPSIMD get SVE for VL %d\n",
						      vec_types[i].name, vl);
			}
		}
	}

	ret = EXIT_SUCCESS;

error:
	kill(child, SIGKILL);

disappeared:
	return ret;
}

int main(void)
{
	int ret = EXIT_SUCCESS;
	pid_t child;

	srandom(getpid());

	ksft_print_header();
	ksft_set_plan(EXPECTED_TESTS);

	if (!(getauxval(AT_HWCAP) & HWCAP_SVE))
		ksft_exit_skip("SVE not available\n");

	child = fork();
	if (!child)
		return do_child();

	if (do_parent(child))
		ret = EXIT_FAILURE;

	ksft_print_cnts();

	return ret;
}