Merge branch 'for-next/kselftest' into for-next/core

* for-next/kselftest:
  selftests: arm64: Factor out utility functions for assembly FP tests
  selftests: arm64: Add coverage of ptrace flags for SVE VL inheritance
  selftests: arm64: Verify that all possible vector lengths are handled
  selftests: arm64: Fix and enable test for setting current VL in vec-syscfg
  selftests: arm64: Remove bogus error check on writing to files
  selftests: arm64: Fix printf() format mismatch in vec-syscfg
  selftests: arm64: Move FPSIMD in SVE ptrace test into a function
  selftests: arm64: More comprehensively test the SVE ptrace interface
  selftests: arm64: Verify interoperation of SVE and FPSIMD register sets
  selftests: arm64: Clarify output when verifying SVE register set
  selftests: arm64: Document what the SVE ptrace test is doing
  selftests: arm64: Remove extraneous register setting code
  selftests: arm64: Don't log child creation as a test in SVE ptrace test
  selftests: arm64: Use a define for the number of SVE ptrace tests to be run
This commit is contained in:
Will Deacon 2021-10-29 12:24:53 +01:00
commit 082f6b4b62
9 changed files with 642 additions and 526 deletions

View File

@ -9,12 +9,12 @@ TEST_PROGS_EXTENDED := fpsimd-test fpsimd-stress \
all: $(TEST_GEN_PROGS) $(TEST_PROGS_EXTENDED) all: $(TEST_GEN_PROGS) $(TEST_PROGS_EXTENDED)
fpsimd-test: fpsimd-test.o fpsimd-test: fpsimd-test.o asm-utils.o
$(CC) -nostdlib $^ -o $@ $(CC) -nostdlib $^ -o $@
rdvl-sve: rdvl-sve.o rdvl.o rdvl-sve: rdvl-sve.o rdvl.o
sve-ptrace: sve-ptrace.o sve-ptrace-asm.o sve-ptrace: sve-ptrace.o
sve-probe-vls: sve-probe-vls.o rdvl.o sve-probe-vls: sve-probe-vls.o rdvl.o
sve-test: sve-test.o sve-test: sve-test.o asm-utils.o
$(CC) -nostdlib $^ -o $@ $(CC) -nostdlib $^ -o $@
vec-syscfg: vec-syscfg.o rdvl.o vec-syscfg: vec-syscfg.o rdvl.o
vlset: vlset.o vlset: vlset.o

View File

@ -1,4 +1,7 @@
- Test unsupported values in the ABIs. - Test unsupported values in the ABIs.
- More coverage for ptrace (eg, vector length conversions). - More coverage for ptrace:
- Coverage for signals. - Get/set of FFR.
- Test PR_SVE_VL_INHERITY after a double fork. - Ensure ptraced processes actually see the register state visible through
the ptrace interface.
- Big endian.
- Test PR_SVE_VL_INHERIT after a double fork.

View File

@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2015-2021 ARM Limited.
// Original author: Dave Martin <Dave.Martin@arm.com>
//
// Utility functions for assembly code.
#include <asm/unistd.h>
#include "assembler.h"
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
.globl putc
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
.globl puts
// Print an unsigned decimal number x0 to stdout
// Clobbers x0-x4,x8
function putdec
mov x1, sp
str x30, [sp, #-32]! // Result can't be > 20 digits
mov x2, #0
strb w2, [x1, #-1]! // Write the NUL terminator
mov x2, #10
0: udiv x3, x0, x2 // div-mod loop to generate the digits
msub x0, x3, x2, x0
add w0, w0, #'0'
strb w0, [x1, #-1]!
mov x0, x3
cbnz x3, 0b
ldrb w0, [x1]
cbnz w0, 1f
mov w0, #'0' // Print "0" for 0, not ""
strb w0, [x1, #-1]!
1: mov x0, x1
bl puts
ldr x30, [sp], #32
ret
endfunction
.globl putdec
// Print an unsigned decimal number x0 to stdout, followed by a newline
// Clobbers x0-x5,x8
function putdecn
mov x5, x30
bl putdec
mov x0, #'\n'
bl putc
ret x5
endfunction
.globl putdecn
// Clobbers x0-x3,x8
function puthexb
str x30, [sp, #-0x10]!
mov w3, w0
lsr w0, w0, #4
bl puthexnibble
mov w0, w3
ldr x30, [sp], #0x10
// fall through to puthexnibble
endfunction
.globl puthexb
// Clobbers x0-x2,x8
function puthexnibble
and w0, w0, #0xf
cmp w0, #10
blo 1f
add w0, w0, #'a' - ('9' + 1)
1: add w0, w0, #'0'
b putc
endfunction
.globl puthexnibble
// x0=data in, x1=size in, clobbers x0-x5,x8
function dumphex
str x30, [sp, #-0x10]!
mov x4, x0
mov x5, x1
0: subs x5, x5, #1
b.lo 1f
ldrb w0, [x4], #1
bl puthexb
b 0b
1: ldr x30, [sp], #0x10
ret
endfunction
.globl dumphex
// Trivial memory copy: copy x2 bytes, starting at address x1, to address x0.
// Clobbers x0-x3
function memcpy
cmp x2, #0
b.eq 1f
0: ldrb w3, [x1], #1
strb w3, [x0], #1
subs x2, x2, #1
b.ne 0b
1: ret
endfunction
.globl memcpy
// Fill x1 bytes starting at x0 with 0xae (for canary purposes)
// Clobbers x1, x2.
function memfill_ae
mov w2, #0xae
b memfill
endfunction
.globl memfill_ae
// Fill x1 bytes starting at x0 with 0.
// Clobbers x1, x2.
function memclr
mov w2, #0
endfunction
.globl memclr
// fall through to memfill
// Trivial memory fill: fill x1 bytes starting at address x0 with byte w2
// Clobbers x1
function memfill
cmp x1, #0
b.eq 1f
0: strb w2, [x0], #1
subs x1, x1, #1
b.ne 0b
1: ret
endfunction
.globl memfill

View File

@ -54,4 +54,15 @@ endfunction
.purgem \name\()_entry .purgem \name\()_entry
.endm .endm
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
#endif /* ! ASSEMBLER_H */ #endif /* ! ASSEMBLER_H */

View File

@ -33,131 +33,6 @@
define_accessor setv, NVR, _vldr define_accessor setv, NVR, _vldr
define_accessor getv, NVR, _vstr define_accessor getv, NVR, _vstr
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
// Print an unsigned decimal number x0 to stdout
// Clobbers x0-x4,x8
function putdec
mov x1, sp
str x30, [sp, #-32]! // Result can't be > 20 digits
mov x2, #0
strb w2, [x1, #-1]! // Write the NUL terminator
mov x2, #10
0: udiv x3, x0, x2 // div-mod loop to generate the digits
msub x0, x3, x2, x0
add w0, w0, #'0'
strb w0, [x1, #-1]!
mov x0, x3
cbnz x3, 0b
ldrb w0, [x1]
cbnz w0, 1f
mov w0, #'0' // Print "0" for 0, not ""
strb w0, [x1, #-1]!
1: mov x0, x1
bl puts
ldr x30, [sp], #32
ret
endfunction
// Print an unsigned decimal number x0 to stdout, followed by a newline
// Clobbers x0-x5,x8
function putdecn
mov x5, x30
bl putdec
mov x0, #'\n'
bl putc
ret x5
endfunction
// Clobbers x0-x3,x8
function puthexb
str x30, [sp, #-0x10]!
mov w3, w0
lsr w0, w0, #4
bl puthexnibble
mov w0, w3
ldr x30, [sp], #0x10
// fall through to puthexnibble
endfunction
// Clobbers x0-x2,x8
function puthexnibble
and w0, w0, #0xf
cmp w0, #10
blo 1f
add w0, w0, #'a' - ('9' + 1)
1: add w0, w0, #'0'
b putc
endfunction
// x0=data in, x1=size in, clobbers x0-x5,x8
function dumphex
str x30, [sp, #-0x10]!
mov x4, x0
mov x5, x1
0: subs x5, x5, #1
b.lo 1f
ldrb w0, [x4], #1
bl puthexb
b 0b
1: ldr x30, [sp], #0x10
ret
endfunction
// Declare some storate space to shadow the SVE register contents: // Declare some storate space to shadow the SVE register contents:
.pushsection .text .pushsection .text
.data .data
@ -168,18 +43,6 @@ scratch:
.space MAXVL_B .space MAXVL_B
.popsection .popsection
// Trivial memory copy: copy x2 bytes, starting at address x1, to address x0.
// Clobbers x0-x3
function memcpy
cmp x2, #0
b.eq 1f
0: ldrb w3, [x1], #1
strb w3, [x0], #1
subs x2, x2, #1
b.ne 0b
1: ret
endfunction
// Generate a test pattern for storage in SVE registers // Generate a test pattern for storage in SVE registers
// x0: pid (16 bits) // x0: pid (16 bits)
// x1: register number (6 bits) // x1: register number (6 bits)
@ -227,33 +90,6 @@ function setup_vreg
ret x4 ret x4
endfunction endfunction
// Fill x1 bytes starting at x0 with 0xae (for canary purposes)
// Clobbers x1, x2.
function memfill_ae
mov w2, #0xae
b memfill
endfunction
// Fill x1 bytes starting at x0 with 0.
// Clobbers x1, x2.
function memclr
mov w2, #0
endfunction
// fall through to memfill
// Trivial memory fill: fill x1 bytes starting at address x0 with byte w2
// Clobbers x1
function memfill
cmp x1, #0
b.eq 1f
0: strb w2, [x0], #1
subs x1, x1, #1
b.ne 0b
1: ret
endfunction
// Trivial memory compare: compare x2 bytes starting at address x0 with // Trivial memory compare: compare x2 bytes starting at address x0 with
// bytes starting at address x1. // bytes starting at address x1.
// Returns only if all bytes match; otherwise, the program is aborted. // Returns only if all bytes match; otherwise, the program is aborted.

View File

@ -1,33 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2015-2019 ARM Limited.
// Original author: Dave Martin <Dave.Martin@arm.com>
#include <asm/unistd.h>
.arch_extension sve
.globl sve_store_patterns
sve_store_patterns:
mov x1, x0
index z0.b, #0, #1
str q0, [x1]
mov w8, #__NR_getpid
svc #0
str q0, [x1, #0x10]
mov z1.d, z0.d
str q0, [x1, #0x20]
mov w8, #__NR_getpid
svc #0
str q0, [x1, #0x30]
mov z1.d, z0.d
str q0, [x1, #0x40]
ret
.size sve_store_patterns, . - sve_store_patterns
.type sve_store_patterns, @function

View File

@ -1,15 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
/* /*
* Copyright (C) 2015-2020 ARM Limited. * Copyright (C) 2015-2021 ARM Limited.
* Original author: Dave Martin <Dave.Martin@arm.com> * Original author: Dave Martin <Dave.Martin@arm.com>
*/ */
#include <errno.h> #include <errno.h>
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/auxv.h> #include <sys/auxv.h>
#include <sys/prctl.h>
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/uio.h> #include <sys/uio.h>
@ -19,40 +21,22 @@
#include "../../kselftest.h" #include "../../kselftest.h"
#define VL_TESTS (((SVE_VQ_MAX - SVE_VQ_MIN) + 1) * 3)
#define FPSIMD_TESTS 5
#define EXPECTED_TESTS (VL_TESTS + FPSIMD_TESTS)
/* <linux/elf.h> and <sys/auxv.h> don't like each other, so: */ /* <linux/elf.h> and <sys/auxv.h> don't like each other, so: */
#ifndef NT_ARM_SVE #ifndef NT_ARM_SVE
#define NT_ARM_SVE 0x405 #define NT_ARM_SVE 0x405
#endif #endif
/* Number of registers filled in by sve_store_patterns */ static void fill_buf(char *buf, size_t size)
#define NR_VREGS 5
void sve_store_patterns(__uint128_t v[NR_VREGS]);
static void dump(const void *buf, size_t size)
{
size_t i;
const unsigned char *p = buf;
for (i = 0; i < size; ++i)
printf(" %.2x", *p++);
}
static int check_vregs(const __uint128_t vregs[NR_VREGS])
{ {
int i; int i;
int ok = 1;
for (i = 0; i < NR_VREGS; ++i) { for (i = 0; i < size; i++)
printf("# v[%d]:", i); buf[i] = random();
dump(&vregs[i], sizeof vregs[i]);
putchar('\n');
if (vregs[i] != vregs[0])
ok = 0;
}
return ok;
} }
static int do_child(void) static int do_child(void)
@ -66,6 +50,15 @@ static int do_child(void)
return EXIT_SUCCESS; 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 struct user_sve_header *get_sve(pid_t pid, void **buf, size_t *size) static struct user_sve_header *get_sve(pid_t pid, void **buf, size_t *size)
{ {
struct user_sve_header *sve; struct user_sve_header *sve;
@ -112,25 +105,335 @@ static int set_sve(pid_t pid, const struct user_sve_header *sve)
return ptrace(PTRACE_SETREGSET, pid, NT_ARM_SVE, &iov); return ptrace(PTRACE_SETREGSET, pid, NT_ARM_SVE, &iov);
} }
static void dump_sve_regs(const struct user_sve_header *sve, unsigned int num, /* Validate setting and getting the inherit flag */
unsigned int vlmax) static void ptrace_set_get_inherit(pid_t child)
{ {
unsigned int vq; struct user_sve_header sve;
unsigned int i; struct user_sve_header *new_sve = NULL;
size_t new_sve_size = 0;
int ret;
if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_SVE) /* First set the flag */
ksft_exit_fail_msg("Dumping non-SVE register\n"); memset(&sve, 0, sizeof(sve));
sve.size = sizeof(sve);
if (vlmax > sve->vl) sve.vl = sve_vl_from_vq(SVE_VQ_MIN);
vlmax = sve->vl; sve.flags = SVE_PT_VL_INHERIT;
ret = set_sve(child, &sve);
vq = sve_vq_from_vl(sve->vl); if (ret != 0) {
for (i = 0; i < num; ++i) { ksft_test_result_fail("Failed to set SVE_PT_VL_INHERIT\n");
printf("# z%u:", i); return;
dump((const char *)sve + SVE_PT_SVE_ZREG_OFFSET(vq, i),
vlmax);
printf("%s\n", vlmax == sve->vl ? "" : " ...");
} }
/*
* Read back the new register state and verify that we have
* set the flags we expected.
*/
if (!get_sve(child, (void **)&new_sve, &new_sve_size)) {
ksft_test_result_fail("Failed to read SVE flags\n");
return;
}
ksft_test_result(new_sve->flags & SVE_PT_VL_INHERIT,
"SVE_PT_VL_INHERIT set\n");
/* Now clear */
sve.flags &= ~SVE_PT_VL_INHERIT;
ret = set_sve(child, &sve);
if (ret != 0) {
ksft_test_result_fail("Failed to clear SVE_PT_VL_INHERIT\n");
return;
}
if (!get_sve(child, (void **)&new_sve, &new_sve_size)) {
ksft_test_result_fail("Failed to read SVE flags\n");
return;
}
ksft_test_result(!(new_sve->flags & SVE_PT_VL_INHERIT),
"SVE_PT_VL_INHERIT cleared\n");
free(new_sve);
}
/* Validate attempting to set the specfied VL via ptrace */
static void ptrace_set_get_vl(pid_t child, 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(PR_SVE_SET_VL, vl);
if (prctl_vl == -1)
ksft_exit_fail_msg("prctl(PR_SVE_SET_VL) failed: %s (%d)\n",
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, &sve);
if (ret != 0) {
ksft_test_result_fail("Failed to set VL %u\n", 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, (void **)&new_sve, &new_sve_size)) {
ksft_test_result_fail("Failed to read VL %u\n", vl);
return;
}
ksft_test_result(new_sve->vl = prctl_vl, "Set VL %u\n", 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)
{
void *svebuf = NULL;
size_t svebufsz = 0;
struct user_sve_header *sve;
struct user_fpsimd_state *fpsimd, new_fpsimd;
unsigned int i, j;
unsigned char *p;
/* New process should start with FPSIMD registers only */
sve = get_sve(child, &svebuf, &svebufsz);
if (!sve) {
ksft_test_result_fail("get_sve: %s\n", strerror(errno));
return;
} else {
ksft_test_result_pass("get_sve(FPSIMD)\n");
}
ksft_test_result((sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD,
"Set FPSIMD registers\n");
if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_FPSIMD)
goto out;
/* 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;
}
if (set_sve(child, sve)) {
ksft_test_result_fail("set_sve(FPSIMD): %s\n",
strerror(errno));
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("get_fpsimd() gave same state\n");
else
ksft_test_result_fail("get_fpsimd() gave different state\n");
out:
free(svebuf);
}
/* Validate attempting to set SVE data and read SVE data */
static void ptrace_set_sve_get_sve_data(pid_t child, 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 VL %u\n",
data_size, 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, write_sve);
if (ret != 0) {
ksft_test_result_fail("Failed to set VL %u data\n", vl);
goto out;
}
/* Read the data back */
if (!get_sve(child, (void **)&read_buf, &read_sve_size)) {
ksft_test_result_fail("Failed to read VL %u data\n", 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("Wrote %d bytes, only read %d\n",
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 SVE data for VL %u\n", vl);
out_read:
free(read_buf);
out:
free(write_buf);
}
/* Validate attempting to set SVE data and read SVE data */
static void ptrace_set_sve_get_fpsimd_data(pid_t child, 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 VL %u\n",
data_size, 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, write_sve);
if (ret != 0) {
ksft_test_result_fail("Failed to set VL %u data\n", vl);
goto out;
}
/* Read the data back */
if (get_fpsimd(child, &fpsimd_state)) {
ksft_test_result_fail("Failed to read VL %u FPSIMD data\n",
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 VL %u Z%d\n", 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 VL %u\n",
vl);
out:
free(write_buf);
} }
static int do_parent(pid_t child) static int do_parent(pid_t child)
@ -139,13 +442,8 @@ static int do_parent(pid_t child)
pid_t pid; pid_t pid;
int status; int status;
siginfo_t si; siginfo_t si;
void *svebuf = NULL, *newsvebuf; unsigned int vq, vl;
size_t svebufsz = 0, newsvebufsz; bool vl_supported;
struct user_sve_header *sve, *new_sve;
struct user_fpsimd_state *fpsimd;
unsigned int i, j;
unsigned char *p;
unsigned int vq;
/* Attach to the child */ /* Attach to the child */
while (1) { while (1) {
@ -167,8 +465,6 @@ static int do_parent(pid_t child)
if (WIFEXITED(status) || WIFSIGNALED(status)) if (WIFEXITED(status) || WIFSIGNALED(status))
ksft_exit_fail_msg("Child died unexpectedly\n"); ksft_exit_fail_msg("Child died unexpectedly\n");
ksft_test_result(WIFSTOPPED(status), "WIFSTOPPED(%d)\n",
status);
if (!WIFSTOPPED(status)) if (!WIFSTOPPED(status))
goto error; goto error;
@ -203,98 +499,27 @@ static int do_parent(pid_t child)
} }
} }
sve = get_sve(pid, &svebuf, &svebufsz); /* FPSIMD via SVE regset */
if (!sve) { ptrace_sve_fpsimd(child);
int e = errno;
ksft_test_result_fail("get_sve: %s\n", strerror(errno)); /* prctl() flags */
if (e == ESRCH) ptrace_set_get_inherit(child);
goto disappeared;
goto error; /* Step through every possible VQ */
} else { for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) {
ksft_test_result_pass("get_sve\n"); vl = sve_vl_from_vq(vq);
}
ksft_test_result((sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD, /* First, try to set this vector length */
"FPSIMD registers\n"); ptrace_set_get_vl(child, vl, &vl_supported);
if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_FPSIMD)
goto error;
fpsimd = (struct user_fpsimd_state *)((char *)sve + /* If the VL is supported validate data set/get */
SVE_PT_FPSIMD_OFFSET); if (vl_supported) {
for (i = 0; i < 32; ++i) { ptrace_set_sve_get_sve_data(child, vl);
p = (unsigned char *)&fpsimd->vregs[i]; ptrace_set_sve_get_fpsimd_data(child, vl);
} else {
for (j = 0; j < sizeof fpsimd->vregs[i]; ++j) ksft_test_result_skip("set SVE get SVE for VL %d\n", vl);
p[j] = j; ksft_test_result_skip("set SVE get FPSIMD for VL %d\n", vl);
} }
if (set_sve(pid, sve)) {
int e = errno;
ksft_test_result_fail("set_sve(FPSIMD): %s\n",
strerror(errno));
if (e == ESRCH)
goto disappeared;
goto error;
}
vq = sve_vq_from_vl(sve->vl);
newsvebufsz = SVE_PT_SVE_ZREG_OFFSET(vq, 1);
new_sve = newsvebuf = malloc(newsvebufsz);
if (!new_sve) {
errno = ENOMEM;
perror(NULL);
goto error;
}
*new_sve = *sve;
new_sve->flags &= ~SVE_PT_REGS_MASK;
new_sve->flags |= SVE_PT_REGS_SVE;
memset((char *)new_sve + SVE_PT_SVE_ZREG_OFFSET(vq, 0),
0, SVE_PT_SVE_ZREG_SIZE(vq));
new_sve->size = SVE_PT_SVE_ZREG_OFFSET(vq, 1);
if (set_sve(pid, new_sve)) {
int e = errno;
ksft_test_result_fail("set_sve(ZREG): %s\n", strerror(errno));
if (e == ESRCH)
goto disappeared;
goto error;
}
new_sve = get_sve(pid, &newsvebuf, &newsvebufsz);
if (!new_sve) {
int e = errno;
ksft_test_result_fail("get_sve(ZREG): %s\n", strerror(errno));
if (e == ESRCH)
goto disappeared;
goto error;
}
ksft_test_result((new_sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_SVE,
"SVE registers\n");
if ((new_sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_SVE)
goto error;
dump_sve_regs(new_sve, 3, sizeof fpsimd->vregs[0]);
p = (unsigned char *)new_sve + SVE_PT_SVE_ZREG_OFFSET(vq, 1);
for (i = 0; i < sizeof fpsimd->vregs[0]; ++i) {
unsigned char expected = i;
if (__BYTE_ORDER == __BIG_ENDIAN)
expected = sizeof fpsimd->vregs[0] - 1 - expected;
ksft_test_result(p[i] == expected, "p[%d] == expected\n", i);
if (p[i] != expected)
goto error;
} }
ret = EXIT_SUCCESS; ret = EXIT_SUCCESS;
@ -309,20 +534,16 @@ static int do_parent(pid_t child)
int main(void) int main(void)
{ {
int ret = EXIT_SUCCESS; int ret = EXIT_SUCCESS;
__uint128_t v[NR_VREGS];
pid_t child; pid_t child;
srandom(getpid());
ksft_print_header(); ksft_print_header();
ksft_set_plan(20); ksft_set_plan(EXPECTED_TESTS);
if (!(getauxval(AT_HWCAP) & HWCAP_SVE)) if (!(getauxval(AT_HWCAP) & HWCAP_SVE))
ksft_exit_skip("SVE not available\n"); ksft_exit_skip("SVE not available\n");
sve_store_patterns(v);
if (!check_vregs(v))
ksft_exit_fail_msg("Initial check_vregs() failed\n");
child = fork(); child = fork();
if (!child) if (!child)
return do_child(); return do_child();

View File

@ -46,130 +46,6 @@ define_accessor getz, NZR, _sve_str_v
define_accessor setp, NPR, _sve_ldr_p define_accessor setp, NPR, _sve_ldr_p
define_accessor getp, NPR, _sve_str_p define_accessor getp, NPR, _sve_str_p
// Print a single character x0 to stdout
// Clobbers x0-x2,x8
function putc
str x0, [sp, #-16]!
mov x0, #1 // STDOUT_FILENO
mov x1, sp
mov x2, #1
mov x8, #__NR_write
svc #0
add sp, sp, #16
ret
endfunction
// Print a NUL-terminated string starting at address x0 to stdout
// Clobbers x0-x3,x8
function puts
mov x1, x0
mov x2, #0
0: ldrb w3, [x0], #1
cbz w3, 1f
add x2, x2, #1
b 0b
1: mov w0, #1 // STDOUT_FILENO
mov x8, #__NR_write
svc #0
ret
endfunction
// Utility macro to print a literal string
// Clobbers x0-x4,x8
.macro puts string
.pushsection .rodata.str1.1, "aMS", 1
.L__puts_literal\@: .string "\string"
.popsection
ldr x0, =.L__puts_literal\@
bl puts
.endm
// Print an unsigned decimal number x0 to stdout
// Clobbers x0-x4,x8
function putdec
mov x1, sp
str x30, [sp, #-32]! // Result can't be > 20 digits
mov x2, #0
strb w2, [x1, #-1]! // Write the NUL terminator
mov x2, #10
0: udiv x3, x0, x2 // div-mod loop to generate the digits
msub x0, x3, x2, x0
add w0, w0, #'0'
strb w0, [x1, #-1]!
mov x0, x3
cbnz x3, 0b
ldrb w0, [x1]
cbnz w0, 1f
mov w0, #'0' // Print "0" for 0, not ""
strb w0, [x1, #-1]!
1: mov x0, x1
bl puts
ldr x30, [sp], #32
ret
endfunction
// Print an unsigned decimal number x0 to stdout, followed by a newline
// Clobbers x0-x5,x8
function putdecn
mov x5, x30
bl putdec
mov x0, #'\n'
bl putc
ret x5
endfunction
// Clobbers x0-x3,x8
function puthexb
str x30, [sp, #-0x10]!
mov w3, w0
lsr w0, w0, #4
bl puthexnibble
mov w0, w3
ldr x30, [sp], #0x10
// fall through to puthexnibble
endfunction
// Clobbers x0-x2,x8
function puthexnibble
and w0, w0, #0xf
cmp w0, #10
blo 1f
add w0, w0, #'a' - ('9' + 1)
1: add w0, w0, #'0'
b putc
endfunction
// x0=data in, x1=size in, clobbers x0-x5,x8
function dumphex
str x30, [sp, #-0x10]!
mov x4, x0
mov x5, x1
0: subs x5, x5, #1
b.lo 1f
ldrb w0, [x4], #1
bl puthexb
b 0b
1: ldr x30, [sp], #0x10
ret
endfunction
// Declare some storate space to shadow the SVE register contents: // Declare some storate space to shadow the SVE register contents:
.pushsection .text .pushsection .text
.data .data
@ -184,18 +60,6 @@ scratch:
.space MAXVL_B .space MAXVL_B
.popsection .popsection
// Trivial memory copy: copy x2 bytes, starting at address x1, to address x0.
// Clobbers x0-x3
function memcpy
cmp x2, #0
b.eq 1f
0: ldrb w3, [x1], #1
strb w3, [x0], #1
subs x2, x2, #1
b.ne 0b
1: ret
endfunction
// Generate a test pattern for storage in SVE registers // Generate a test pattern for storage in SVE registers
// x0: pid (16 bits) // x0: pid (16 bits)
// x1: register number (6 bits) // x1: register number (6 bits)
@ -316,33 +180,6 @@ function setup_ffr
ret x4 ret x4
endfunction endfunction
// Fill x1 bytes starting at x0 with 0xae (for canary purposes)
// Clobbers x1, x2.
function memfill_ae
mov w2, #0xae
b memfill
endfunction
// Fill x1 bytes starting at x0 with 0.
// Clobbers x1, x2.
function memclr
mov w2, #0
endfunction
// fall through to memfill
// Trivial memory fill: fill x1 bytes starting at address x0 with byte w2
// Clobbers x1
function memfill
cmp x1, #0
b.eq 1f
0: strb w2, [x0], #1
subs x1, x1, #1
b.ne 0b
1: ret
endfunction
// Trivial memory compare: compare x2 bytes starting at address x0 with // Trivial memory compare: compare x2 bytes starting at address x0 with
// bytes starting at address x1. // bytes starting at address x1.
// Returns only if all bytes match; otherwise, the program is aborted. // Returns only if all bytes match; otherwise, the program is aborted.

View File

@ -109,7 +109,7 @@ static int get_child_rdvl(struct vec_data *data)
/* exec() a new binary which puts the VL on stdout */ /* exec() a new binary which puts the VL on stdout */
ret = execl(data->rdvl_binary, data->rdvl_binary, NULL); ret = execl(data->rdvl_binary, data->rdvl_binary, NULL);
fprintf(stderr, "execl(%s) failed: %d\n", fprintf(stderr, "execl(%s) failed: %d (%s)\n",
data->rdvl_binary, errno, strerror(errno)); data->rdvl_binary, errno, strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -180,7 +180,6 @@ static int file_read_integer(const char *name, int *val)
static int file_write_integer(const char *name, int val) static int file_write_integer(const char *name, int val)
{ {
FILE *f; FILE *f;
int ret;
f = fopen(name, "w"); f = fopen(name, "w");
if (!f) { if (!f) {
@ -192,11 +191,6 @@ static int file_write_integer(const char *name, int val)
fprintf(f, "%d", val); fprintf(f, "%d", val);
fclose(f); fclose(f);
if (ret < 0) {
ksft_test_result_fail("Error writing %d to %s\n",
val, name);
return -1;
}
return 0; return 0;
} }
@ -335,12 +329,9 @@ static void prctl_set_same(struct vec_data *data)
return; return;
} }
if (cur_vl != data->rdvl()) ksft_test_result(cur_vl == data->rdvl(),
ksft_test_result_pass("%s current VL is %d\n", "%s set VL %d and have VL %d\n",
data->name, ret); data->name, cur_vl, data->rdvl());
else
ksft_test_result_fail("%s prctl() VL %d but RDVL is %d\n",
data->name, ret, data->rdvl());
} }
/* Can we set a new VL for this process? */ /* Can we set a new VL for this process? */
@ -549,6 +540,82 @@ static void prctl_set_onexec(struct vec_data *data)
file_write_integer(data->default_vl_file, data->default_vl); file_write_integer(data->default_vl_file, data->default_vl);
} }
/* For each VQ verify that setting via prctl() does the right thing */
static void prctl_set_all_vqs(struct vec_data *data)
{
int ret, vq, vl, new_vl;
int errors = 0;
if (!data->min_vl || !data->max_vl) {
ksft_test_result_skip("%s Failed to enumerate VLs, not testing VL setting\n",
data->name);
return;
}
for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) {
vl = sve_vl_from_vq(vq);
/* Attempt to set the VL */
ret = prctl(data->prctl_set, vl);
if (ret < 0) {
errors++;
ksft_print_msg("%s prctl set failed for %d: %d (%s)\n",
data->name, vl,
errno, strerror(errno));
continue;
}
new_vl = ret & PR_SVE_VL_LEN_MASK;
/* Check that we actually have the reported new VL */
if (data->rdvl() != new_vl) {
ksft_print_msg("Set %s VL %d but RDVL reports %d\n",
data->name, new_vl, data->rdvl());
errors++;
}
/* Was that the VL we asked for? */
if (new_vl == vl)
continue;
/* Should round up to the minimum VL if below it */
if (vl < data->min_vl) {
if (new_vl != data->min_vl) {
ksft_print_msg("%s VL %d returned %d not minimum %d\n",
data->name, vl, new_vl,
data->min_vl);
errors++;
}
continue;
}
/* Should round down to maximum VL if above it */
if (vl > data->max_vl) {
if (new_vl != data->max_vl) {
ksft_print_msg("%s VL %d returned %d not maximum %d\n",
data->name, vl, new_vl,
data->max_vl);
errors++;
}
continue;
}
/* Otherwise we should've rounded down */
if (!(new_vl < vl)) {
ksft_print_msg("%s VL %d returned %d, did not round down\n",
data->name, vl, new_vl);
errors++;
continue;
}
}
ksft_test_result(errors == 0, "%s prctl() set all VLs, %d errors\n",
data->name, errors);
}
typedef void (*test_type)(struct vec_data *); typedef void (*test_type)(struct vec_data *);
static const test_type tests[] = { static const test_type tests[] = {
@ -561,10 +628,12 @@ static const test_type tests[] = {
proc_write_max, proc_write_max,
prctl_get, prctl_get,
prctl_set_same,
prctl_set, prctl_set,
prctl_set_no_child, prctl_set_no_child,
prctl_set_for_child, prctl_set_for_child,
prctl_set_onexec, prctl_set_onexec,
prctl_set_all_vqs,
}; };
int main(void) int main(void)