mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-16 08:44:21 +08:00
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:
commit
082f6b4b62
@ -9,12 +9,12 @@ TEST_PROGS_EXTENDED := fpsimd-test fpsimd-stress \
|
||||
|
||||
all: $(TEST_GEN_PROGS) $(TEST_PROGS_EXTENDED)
|
||||
|
||||
fpsimd-test: fpsimd-test.o
|
||||
fpsimd-test: fpsimd-test.o asm-utils.o
|
||||
$(CC) -nostdlib $^ -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-test: sve-test.o
|
||||
sve-test: sve-test.o asm-utils.o
|
||||
$(CC) -nostdlib $^ -o $@
|
||||
vec-syscfg: vec-syscfg.o rdvl.o
|
||||
vlset: vlset.o
|
||||
|
@ -1,4 +1,7 @@
|
||||
- Test unsupported values in the ABIs.
|
||||
- More coverage for ptrace (eg, vector length conversions).
|
||||
- Coverage for signals.
|
||||
- Test PR_SVE_VL_INHERITY after a double fork.
|
||||
- More coverage for ptrace:
|
||||
- Get/set of FFR.
|
||||
- Ensure ptraced processes actually see the register state visible through
|
||||
the ptrace interface.
|
||||
- Big endian.
|
||||
- Test PR_SVE_VL_INHERIT after a double fork.
|
||||
|
172
tools/testing/selftests/arm64/fp/asm-utils.S
Normal file
172
tools/testing/selftests/arm64/fp/asm-utils.S
Normal 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
|
@ -54,4 +54,15 @@ endfunction
|
||||
.purgem \name\()_entry
|
||||
.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 */
|
||||
|
@ -33,131 +33,6 @@
|
||||
define_accessor setv, NVR, _vldr
|
||||
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:
|
||||
.pushsection .text
|
||||
.data
|
||||
@ -168,18 +43,6 @@ scratch:
|
||||
.space MAXVL_B
|
||||
.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
|
||||
// x0: pid (16 bits)
|
||||
// x1: register number (6 bits)
|
||||
@ -227,33 +90,6 @@ function setup_vreg
|
||||
ret x4
|
||||
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
|
||||
// bytes starting at address x1.
|
||||
// Returns only if all bytes match; otherwise, the program is aborted.
|
||||
|
@ -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
|
@ -1,15 +1,17 @@
|
||||
// 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>
|
||||
*/
|
||||
#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>
|
||||
@ -19,40 +21,22 @@
|
||||
|
||||
#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: */
|
||||
#ifndef NT_ARM_SVE
|
||||
#define NT_ARM_SVE 0x405
|
||||
#endif
|
||||
|
||||
/* Number of registers filled in by sve_store_patterns */
|
||||
#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])
|
||||
static void fill_buf(char *buf, size_t size)
|
||||
{
|
||||
int i;
|
||||
int ok = 1;
|
||||
|
||||
for (i = 0; i < NR_VREGS; ++i) {
|
||||
printf("# v[%d]:", i);
|
||||
dump(&vregs[i], sizeof vregs[i]);
|
||||
putchar('\n');
|
||||
|
||||
if (vregs[i] != vregs[0])
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
return ok;
|
||||
for (i = 0; i < size; i++)
|
||||
buf[i] = random();
|
||||
}
|
||||
|
||||
static int do_child(void)
|
||||
@ -66,6 +50,15 @@ static int do_child(void)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static void dump_sve_regs(const struct user_sve_header *sve, unsigned int num,
|
||||
unsigned int vlmax)
|
||||
/* Validate setting and getting the inherit flag */
|
||||
static void ptrace_set_get_inherit(pid_t child)
|
||||
{
|
||||
unsigned int vq;
|
||||
unsigned int i;
|
||||
struct user_sve_header sve;
|
||||
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)
|
||||
ksft_exit_fail_msg("Dumping non-SVE register\n");
|
||||
|
||||
if (vlmax > sve->vl)
|
||||
vlmax = sve->vl;
|
||||
|
||||
vq = sve_vq_from_vl(sve->vl);
|
||||
for (i = 0; i < num; ++i) {
|
||||
printf("# z%u:", i);
|
||||
dump((const char *)sve + SVE_PT_SVE_ZREG_OFFSET(vq, i),
|
||||
vlmax);
|
||||
printf("%s\n", vlmax == sve->vl ? "" : " ...");
|
||||
/* 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, &sve);
|
||||
if (ret != 0) {
|
||||
ksft_test_result_fail("Failed to set SVE_PT_VL_INHERIT\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
@ -139,13 +442,8 @@ static int do_parent(pid_t child)
|
||||
pid_t pid;
|
||||
int status;
|
||||
siginfo_t si;
|
||||
void *svebuf = NULL, *newsvebuf;
|
||||
size_t svebufsz = 0, newsvebufsz;
|
||||
struct user_sve_header *sve, *new_sve;
|
||||
struct user_fpsimd_state *fpsimd;
|
||||
unsigned int i, j;
|
||||
unsigned char *p;
|
||||
unsigned int vq;
|
||||
unsigned int vq, vl;
|
||||
bool vl_supported;
|
||||
|
||||
/* Attach to the child */
|
||||
while (1) {
|
||||
@ -167,8 +465,6 @@ static int do_parent(pid_t child)
|
||||
if (WIFEXITED(status) || WIFSIGNALED(status))
|
||||
ksft_exit_fail_msg("Child died unexpectedly\n");
|
||||
|
||||
ksft_test_result(WIFSTOPPED(status), "WIFSTOPPED(%d)\n",
|
||||
status);
|
||||
if (!WIFSTOPPED(status))
|
||||
goto error;
|
||||
|
||||
@ -203,98 +499,27 @@ static int do_parent(pid_t child)
|
||||
}
|
||||
}
|
||||
|
||||
sve = get_sve(pid, &svebuf, &svebufsz);
|
||||
if (!sve) {
|
||||
int e = errno;
|
||||
/* FPSIMD via SVE regset */
|
||||
ptrace_sve_fpsimd(child);
|
||||
|
||||
ksft_test_result_fail("get_sve: %s\n", strerror(errno));
|
||||
if (e == ESRCH)
|
||||
goto disappeared;
|
||||
/* prctl() flags */
|
||||
ptrace_set_get_inherit(child);
|
||||
|
||||
goto error;
|
||||
} else {
|
||||
ksft_test_result_pass("get_sve\n");
|
||||
}
|
||||
/* Step through every possible VQ */
|
||||
for (vq = SVE_VQ_MIN; vq <= SVE_VQ_MAX; vq++) {
|
||||
vl = sve_vl_from_vq(vq);
|
||||
|
||||
ksft_test_result((sve->flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD,
|
||||
"FPSIMD registers\n");
|
||||
if ((sve->flags & SVE_PT_REGS_MASK) != SVE_PT_REGS_FPSIMD)
|
||||
goto error;
|
||||
/* First, try to set this vector length */
|
||||
ptrace_set_get_vl(child, vl, &vl_supported);
|
||||
|
||||
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(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;
|
||||
/* If the VL is supported validate data set/get */
|
||||
if (vl_supported) {
|
||||
ptrace_set_sve_get_sve_data(child, vl);
|
||||
ptrace_set_sve_get_fpsimd_data(child, vl);
|
||||
} else {
|
||||
ksft_test_result_skip("set SVE get SVE for VL %d\n", vl);
|
||||
ksft_test_result_skip("set SVE get FPSIMD for VL %d\n", vl);
|
||||
}
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
@ -309,20 +534,16 @@ disappeared:
|
||||
int main(void)
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
__uint128_t v[NR_VREGS];
|
||||
pid_t child;
|
||||
|
||||
srandom(getpid());
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(20);
|
||||
ksft_set_plan(EXPECTED_TESTS);
|
||||
|
||||
if (!(getauxval(AT_HWCAP) & HWCAP_SVE))
|
||||
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();
|
||||
if (!child)
|
||||
return do_child();
|
||||
|
@ -46,130 +46,6 @@ define_accessor getz, NZR, _sve_str_v
|
||||
define_accessor setp, NPR, _sve_ldr_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:
|
||||
.pushsection .text
|
||||
.data
|
||||
@ -184,18 +60,6 @@ scratch:
|
||||
.space MAXVL_B
|
||||
.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
|
||||
// x0: pid (16 bits)
|
||||
// x1: register number (6 bits)
|
||||
@ -316,33 +180,6 @@ function setup_ffr
|
||||
ret x4
|
||||
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
|
||||
// bytes starting at address x1.
|
||||
// Returns only if all bytes match; otherwise, the program is aborted.
|
||||
|
@ -109,7 +109,7 @@ static int get_child_rdvl(struct vec_data *data)
|
||||
|
||||
/* 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\n",
|
||||
fprintf(stderr, "execl(%s) failed: %d (%s)\n",
|
||||
data->rdvl_binary, errno, strerror(errno));
|
||||
|
||||
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)
|
||||
{
|
||||
FILE *f;
|
||||
int ret;
|
||||
|
||||
f = fopen(name, "w");
|
||||
if (!f) {
|
||||
@ -192,11 +191,6 @@ static int file_write_integer(const char *name, int val)
|
||||
|
||||
fprintf(f, "%d", val);
|
||||
fclose(f);
|
||||
if (ret < 0) {
|
||||
ksft_test_result_fail("Error writing %d to %s\n",
|
||||
val, name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -335,12 +329,9 @@ static void prctl_set_same(struct vec_data *data)
|
||||
return;
|
||||
}
|
||||
|
||||
if (cur_vl != data->rdvl())
|
||||
ksft_test_result_pass("%s current VL is %d\n",
|
||||
data->name, ret);
|
||||
else
|
||||
ksft_test_result_fail("%s prctl() VL %d but RDVL is %d\n",
|
||||
data->name, ret, data->rdvl());
|
||||
ksft_test_result(cur_vl == data->rdvl(),
|
||||
"%s set VL %d and have VL %d\n",
|
||||
data->name, cur_vl, data->rdvl());
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* 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 *);
|
||||
|
||||
static const test_type tests[] = {
|
||||
@ -561,10 +628,12 @@ static const test_type tests[] = {
|
||||
proc_write_max,
|
||||
|
||||
prctl_get,
|
||||
prctl_set_same,
|
||||
prctl_set,
|
||||
prctl_set_no_child,
|
||||
prctl_set_for_child,
|
||||
prctl_set_onexec,
|
||||
prctl_set_all_vqs,
|
||||
};
|
||||
|
||||
int main(void)
|
||||
|
Loading…
Reference in New Issue
Block a user