// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 ARM Limited.
* Original author: Mark Brown <broonie@kernel.org>
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.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/types.h>
#include <sys/wait.h>
#include <asm/sigcontext.h>
#include <asm/hwcap.h>
#include "kselftest.h"
#include "rdvl.h"
#define ARCH_MIN_VL SVE_VL_MIN
struct vec_data {
const char *name;
unsigned long hwcap_type;
unsigned long hwcap;
const char *rdvl_binary;
int (*rdvl)(void);
int prctl_get;
int prctl_set;
const char *default_vl_file;
int default_vl;
int min_vl;
int max_vl;
};
#define VEC_SVE 0
#define VEC_SME 1
static struct vec_data vec_data[] = {
[VEC_SVE] = {
.name = "SVE",
.hwcap_type = AT_HWCAP,
.hwcap = HWCAP_SVE,
.rdvl = rdvl_sve,
.rdvl_binary = "./rdvl-sve",
.prctl_get = PR_SVE_GET_VL,
.prctl_set = PR_SVE_SET_VL,
.default_vl_file = "/proc/sys/abi/sve_default_vector_length",
},
[VEC_SME] = {
.name = "SME",
.hwcap_type = AT_HWCAP2,
.hwcap = HWCAP2_SME,
.rdvl = rdvl_sme,
.rdvl_binary = "./rdvl-sme",
.prctl_get = PR_SME_GET_VL,
.prctl_set = PR_SME_SET_VL,
.default_vl_file = "/proc/sys/abi/sme_default_vector_length",
},
};
static bool vec_type_supported(struct vec_data *data)
{
return getauxval(data->hwcap_type) & data->hwcap;
}
static int stdio_read_integer(FILE *f, const char *what, int *val)
{
int n = 0;
int ret;
ret = fscanf(f, "%d%*1[\n]%n", val, &n);
if (ret < 1 || n < 1) {
ksft_print_msg("failed to parse integer from %s\n", what);
return -1;
}
return 0;
}
/* Start a new process and return the vector length it sees */
static int get_child_rdvl(struct vec_data *data)
{
FILE *out;
int pipefd[2];
pid_t pid, child;
int read_vl, ret;
ret = pipe(pipefd);
if (ret == -1) {
ksft_print_msg("pipe() failed: %d (%s)\n",
errno, strerror(errno));
return -1;
}
fflush(stdout);
child = fork();
if (child == -1) {
ksft_print_msg("fork() failed: %d (%s)\n",
errno, strerror(errno));
close(pipefd[0]);
close(pipefd[1]);
return -1;
}
/* Child: put vector length on the pipe */
if (child == 0) {
/*
* Replace stdout with the pipe, errors to stderr from
* here as kselftest prints to stdout.
*/
ret = dup2(pipefd[1], 1);
if (ret == -1) {
fprintf(stderr, "dup2() %d\n", errno);
exit(EXIT_FAILURE);
}
/* exec() a new binary which puts the VL on stdout */
ret = execl(data->rdvl_binary, data->rdvl_binary, NULL);
fprintf(stderr, "execl(%s) failed: %d (%s)\n",
data->rdvl_binary, errno, strerror(errno));
exit(EXIT_FAILURE);
}
close(pipefd[1]);
/* Parent; wait for the exit status from the child & verify it */
do {
pid = wait(&ret);
if (pid == -1) {
ksft_print_msg("wait() failed: %d (%s)\n",
errno, strerror(errno));
close(pipefd[0]);
return -1;
}
} while (pid != child);
assert(pid == child);
if (!WIFEXITED(ret)) {
ksft_print_msg("child exited abnormally\n");
close(pipefd[0]);
return -1;
}
if (WEXITSTATUS(ret) != 0) {
ksft_print_msg("child returned error %d\n",
WEXITSTATUS(ret));
close(pipefd[0]);
return -1;
}
out = fdopen(pipefd[0], "r");
if (!out) {
ksft_print_msg("failed to open child stdout\n");
close(pipefd[0]);
return -1;
}
ret = stdio_read_integer(out, "child", &read_vl);
fclose(out);
if (ret != 0)
return ret;
return read_vl;
}
static int file_read_integer(const char *name, int *val)
{
FILE *f;
int ret;
f = fopen(name, "r");
if (!f) {
ksft_test_result_fail("Unable to open %s: %d (%s)\n",
name, errno,
strerror(errno));
return -1;
}
ret = stdio_read_integer(f, name, val);
fclose(f);
return ret;
}
static int file_write_integer(const char *name, int val)
{
FILE