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)
|
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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
.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 */
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
// 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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue