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

* for-next/selftests:
  kselftest/arm64: Add 2025 dpISA coverage to hwcaps
  kselftest/arm64: Add tests for POR_EL0 save/reset/restore
  kselftest/arm64: Move/add POE helpers to test_signals_utils.h
  kselftest/arm64: Add POE as a feature in the signal tests
  selftests/mm: Fix resv_sz when parsing arm64 signal frame
This commit is contained in:
Will Deacon 2026-06-14 12:18:18 +01:00
commit 68d619cbde
8 changed files with 276 additions and 16 deletions

View File

@ -108,6 +108,24 @@ static void f8mm8_sigill(void)
asm volatile(".inst 0x6e80ec00");
}
static void f16f32dot_sigill(void)
{
/* FDOT V0.2S, V0.4H, V0.2H[0] */
asm volatile(".inst 0xf409000");
}
static void f16f32mm_sigill(void)
{
/* FMMLA V0.4S, V0.8H, V0.8H */
asm volatile(".inst 0x4e40ec00");
}
static void f16mm_sigill(void)
{
/* FMMLA V0.8H, V0.8H, V0.8H */
asm volatile(".inst 0x4ec0ec00");
}
static void faminmax_sigill(void)
{
/* FAMIN V0.4H, V0.4H, V0.4H */
@ -191,6 +209,12 @@ static void lut_sigill(void)
asm volatile(".inst 0x4e801000");
}
static void sve_lut6_sigill(void)
{
/* LUTI6 Z0.H, { Z0.H, Z1.H }, Z0[0] */
asm volatile(".inst 0x4560ac00");
}
static void mops_sigill(void)
{
char dst[1], src[1];
@ -282,6 +306,18 @@ static void sme2p2_sigill(void)
asm volatile("msr S0_3_C4_C6_3, xzr" : : : );
}
static void sme2p3_sigill(void)
{
/* SMSTART SM */
asm volatile("msr S0_3_C4_C3_3, xzr" : : : );
/* ADDQP Z0.B, Z0.B, Z0.B */
asm volatile(".inst 0x4207800" : : : "z0");
/* SMSTOP */
asm volatile("msr S0_3_C4_C6_3, xzr" : : : );
}
static void sme_aes_sigill(void)
{
/* SMSTART SM */
@ -378,6 +414,18 @@ static void smef8f32_sigill(void)
asm volatile("msr S0_3_C4_C6_3, xzr" : : : );
}
static void smelut6_sigill(void)
{
/* SMSTART */
asm volatile("msr S0_3_C4_C7_3, xzr" : : : );
/* LUTI6 { Z0.B-Z3.B }, ZT0, { Z0-Z2 } */
asm volatile(".inst 0xc08a0000" : : : );
/* SMSTOP */
asm volatile("msr S0_3_C4_C6_3, xzr" : : : );
}
static void smelutv2_sigill(void)
{
/* SMSTART */
@ -486,6 +534,12 @@ static void sve2p2_sigill(void)
asm volatile(".inst 0x4cea000" : : : "z0");
}
static void sve2p3_sigill(void)
{
/* ADDQP Z0.B, Z0.B, Z0.B */
asm volatile(".inst 0x4207800" : : : "z0");
}
static void sveaes_sigill(void)
{
/* AESD z0.b, z0.b, z0.b */
@ -504,6 +558,12 @@ static void sveb16b16_sigill(void)
asm volatile(".inst 0x65000000" : : : );
}
static void sveb16mm_sigill(void)
{
/* BFMMLA Z0.H, Z0.H, Z0.H */
asm volatile(".inst 0x64e0e000" : : : );
}
static void svebfscale_sigill(void)
{
/* BFSCALE Z0.H, P0/M, Z0.H, Z0.H */
@ -729,6 +789,27 @@ static const struct hwcap_data {
.cpuinfo = "f8mm4",
.sigill_fn = f8mm4_sigill,
},
{
.name = "F16MM",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_F16MM,
.cpuinfo = "f16mm",
.sigill_fn = f16mm_sigill,
},
{
.name = "F16F32DOT",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_F16F32DOT,
.cpuinfo = "f16f32dot",
.sigill_fn = f16f32dot_sigill,
},
{
.name = "F16F32MM",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_F16F32MM,
.cpuinfo = "f16f32mm",
.sigill_fn = f16f32mm_sigill,
},
{
.name = "FAMINMAX",
.at_hwcap = AT_HWCAP2,
@ -918,6 +999,13 @@ static const struct hwcap_data {
.cpuinfo = "sme2p2",
.sigill_fn = sme2p2_sigill,
},
{
.name = "SME 2.3",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_SME2P3,
.cpuinfo = "sme2p3",
.sigill_fn = sme2p3_sigill,
},
{
.name = "SME AES",
.at_hwcap = AT_HWCAP,
@ -967,6 +1055,13 @@ static const struct hwcap_data {
.cpuinfo = "smef8f32",
.sigill_fn = smef8f32_sigill,
},
{
.name = "SME LUT6",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_SME_LUT6,
.cpuinfo = "smelut6",
.sigill_fn = smelut6_sigill,
},
{
.name = "SME LUTV2",
.at_hwcap = AT_HWCAP2,
@ -1052,6 +1147,13 @@ static const struct hwcap_data {
.cpuinfo = "sve2p2",
.sigill_fn = sve2p2_sigill,
},
{
.name = "SVE 2.3",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_SVE2P3,
.cpuinfo = "sve2p3",
.sigill_fn = sve2p3_sigill,
},
{
.name = "SVE AES",
.at_hwcap = AT_HWCAP2,
@ -1066,6 +1168,13 @@ static const struct hwcap_data {
.cpuinfo = "sveaes2",
.sigill_fn = sveaes2_sigill,
},
{
.name = "SVE B16MM",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_SVE_B16MM,
.cpuinfo = "sveb16mm",
.sigill_fn = sveb16mm_sigill,
},
{
.name = "SVE BFSCALE",
.at_hwcap = AT_HWCAP,
@ -1087,6 +1196,13 @@ static const struct hwcap_data {
.cpuinfo = "svef16mm",
.sigill_fn = svef16mm_sigill,
},
{
.name = "SVE_LUT6",
.at_hwcap = AT_HWCAP3,
.hwcap_bit = HWCAP3_SVE_LUT6,
.cpuinfo = "svelut6",
.sigill_fn = sve_lut6_sigill,
},
{
.name = "SVE2 B16B16",
.at_hwcap = AT_HWCAP2,

View File

@ -36,6 +36,7 @@ enum {
FSME_FA64_BIT,
FSME2_BIT,
FGCS_BIT,
FPOE_BIT,
FMAX_END
};
@ -45,6 +46,7 @@ enum {
#define FEAT_SME_FA64 (1UL << FSME_FA64_BIT)
#define FEAT_SME2 (1UL << FSME2_BIT)
#define FEAT_GCS (1UL << FGCS_BIT)
#define FEAT_POE (1UL << FPOE_BIT)
/*
* A descriptor used to describe and configure a test case.

View File

@ -31,6 +31,7 @@ static char const *const feats_names[FMAX_END] = {
" FA64 ",
" SME2 ",
" GCS ",
" POE ",
};
#define MAX_FEATS_SZ 128
@ -341,6 +342,8 @@ int test_init(struct tdescr *td)
td->feats_supported |= FEAT_SME2;
if (getauxval(AT_HWCAP) & HWCAP_GCS)
td->feats_supported |= FEAT_GCS;
if (getauxval(AT_HWCAP2) & HWCAP2_POE)
td->feats_supported |= FEAT_POE;
if (feats_ok(td)) {
if (td->feats_required & td->feats_supported)
fprintf(stderr,

View File

@ -57,6 +57,22 @@ static inline __attribute__((always_inline)) uint64_t get_gcspr_el0(void)
return val;
}
#define SYS_POR_EL0 "S3_3_C10_C2_4"
static inline uint64_t get_por_el0(void)
{
uint64_t val;
asm volatile("mrs %0, " SYS_POR_EL0 "\n" : "=r"(val));
return val;
}
static inline void set_por_el0(uint64_t val)
{
asm volatile("msr " SYS_POR_EL0 ", %0\n" :: "r"(val));
}
static inline bool feats_ok(struct tdescr *td)
{
if (td->feats_incompatible & td->feats_supported)

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Arm Ltd
*
* Verify that the POR_EL0 register is left untouched on sigreturn if the
* POE frame record is missing.
*/
#include <asm/sigcontext.h>
#include "test_signals_utils.h"
#include "testcases.h"
#define POR_EL0_INIT 0x07ul
#define POR_EL0_CUSTOM 0x77ul
static bool failed_check;
static bool modify_por_el0(struct tdescr *td)
{
set_por_el0(POR_EL0_CUSTOM);
return true;
}
static int signal_remove_poe_context(struct tdescr *td, siginfo_t *si,
ucontext_t *uc)
{
struct _aarch64_ctx *ctx = GET_UC_RESV_HEAD(uc);
size_t resv_size = GET_UCP_RESV_SIZE(uc);
struct _aarch64_ctx *poe_ctx_head;
poe_ctx_head = get_header(ctx, POE_MAGIC, resv_size, NULL);
if (!poe_ctx_head) {
fprintf(stderr, "Missing poe_context record\n");
failed_check = true;
return 0;
}
/*
* Actually removing the record would require moving down the next
* records. An easier option is to turn it into an ESR record, which is
* ignored by sigreturn().
*/
poe_ctx_head->magic = ESR_MAGIC;
return 0;
}
static void check_por_el0_preserved(struct tdescr *td)
{
uint64_t por_el0 = get_por_el0();
if (por_el0 == POR_EL0_INIT) {
fprintf(stderr, "POR_EL0 preserved\n");
} else {
fprintf(stderr, "POR_EL0 unexpectedly set to %lx\n", por_el0);
failed_check = true;
}
td->pass = !failed_check;
}
struct tdescr tde = {
.name = "POR_EL0 missing poe_context",
.descr = "Remove poe_context record and check POR_EL0 is preserved",
.feats_required = FEAT_POE,
.timeout = 3,
.sig_trig = SIGUSR1,
.init = modify_por_el0,
.run = signal_remove_poe_context,
.check_result = check_por_el0_preserved,
};

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Arm Ltd
*
* Verify that the POR_EL0 register is saved and restored as expected on signal
* entry/return.
*/
#include <asm/sigcontext.h>
#include "test_signals_utils.h"
#include "testcases.h"
#define POR_EL0_INIT 0x07ul
#define POR_EL0_CUSTOM 0x77ul
static bool failed_check;
static bool modify_por_el0(struct tdescr *td)
{
set_por_el0(POR_EL0_CUSTOM);
return true;
}
static int signal_check_por_el0_reset(struct tdescr *td, siginfo_t *si,
ucontext_t *uc)
{
uint64_t signal_por_el0 = get_por_el0();
if (signal_por_el0 != POR_EL0_INIT) {
fprintf(stderr, "POR_EL0 is %lx in signal handler (expected %lx)\n",
signal_por_el0, POR_EL0_INIT);
failed_check = true;
}
return 0;
}
static void check_por_el0_restored(struct tdescr *td)
{
uint64_t por_el0 = get_por_el0();
if (por_el0 == POR_EL0_CUSTOM) {
fprintf(stderr, "POR_EL0 restored\n");
} else {
fprintf(stderr, "POR_EL0 was %lx but is now %lx\n",
POR_EL0_CUSTOM, por_el0);
failed_check = true;
}
td->pass = !failed_check;
}
struct tdescr tde = {
.name = "POR_EL0 restore",
.descr = "Validate that POR_EL0 is saved/restored on signal entry/return",
.feats_required = FEAT_POE,
.timeout = 3,
.sig_trig = SIGUSR1,
.init = modify_por_el0,
.run = signal_check_por_el0_reset,
.check_result = check_por_el0_restored,
};

View File

@ -21,21 +21,6 @@ static union {
char buf[1024 * 128];
} context;
#define SYS_POR_EL0 "S3_3_C10_C2_4"
static uint64_t get_por_el0(void)
{
uint64_t val;
asm volatile(
"mrs %0, " SYS_POR_EL0 "\n"
: "=r"(val)
:
: );
return val;
}
int poe_present(struct tdescr *td, siginfo_t *si, ucontext_t *uc)
{
struct _aarch64_ctx *head = GET_BUF_RESV_HEAD(context);

View File

@ -130,9 +130,10 @@ static inline u64 get_pkey_bits(u64 reg, int pkey)
static inline void aarch64_write_signal_pkey(ucontext_t *uctxt, u64 pkey)
{
struct _aarch64_ctx *ctx = GET_UC_RESV_HEAD(uctxt);
size_t resv_size = GET_UCP_RESV_SIZE(uctxt);
struct poe_context *poe_ctx =
(struct poe_context *) get_header(ctx, POE_MAGIC,
sizeof(uctxt->uc_mcontext), NULL);
resv_size, NULL);
if (poe_ctx)
poe_ctx->por_el0 = pkey;
}