blob: 7972478e6a1c8b32635ce17c0004682db3d0a4c3 [file] [log] [blame]
/*
* Copyright (c) 2023-2025, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <arch_features.h>
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <stdlib.h>
#include <sync.h>
#include <lib/extensions/fpu.h>
#include <lib/extensions/sve.h>
#include <realm_helpers.h>
#include <host_realm_simd.h>
#include <host_shared_data.h>
#define RL_SVE_OP_ARRAYSIZE 512U
#define SVE_TEST_ITERATIONS 4U
static int rl_sve_op_1[RL_SVE_OP_ARRAYSIZE];
static int rl_sve_op_2[RL_SVE_OP_ARRAYSIZE];
static sve_z_regs_t rl_sve_z_regs_write;
static sve_z_regs_t rl_sve_z_regs_read;
static sve_p_regs_t rl_sve_p_regs_write;
static sve_p_regs_t rl_sve_p_regs_read;
static sve_ffr_regs_t rl_sve_ffr_regs_write;
static sve_ffr_regs_t rl_sve_ffr_regs_read;
static fpu_cs_regs_t rl_fpu_cs_regs_write;
static fpu_cs_regs_t rl_fpu_cs_regs_read;
static rsi_plane_run run __aligned(PAGE_SIZE);
/* Returns the maximum supported VL. This test is called only by sve Realm */
bool test_realm_sve_rdvl(void)
{
host_shared_data_t *sd = realm_get_my_shared_structure();
struct sve_cmd_rdvl *output;
assert(is_armv8_2_sve_present());
output = (struct sve_cmd_rdvl *)sd->realm_cmd_output_buffer;
memset((void *)output, 0, sizeof(struct sve_cmd_rdvl));
sve_config_vq(SVE_VQ_ARCH_MAX);
output->rdvl = sve_rdvl_1();
return true;
}
/*
* Reads and returns the ID_AA64PFR0_EL1 and ID_AA64ZFR0_EL1 registers
* This test could be called from sve or non-sve Realm
*/
bool test_realm_sve_read_id_registers(void)
{
host_shared_data_t *sd = realm_get_my_shared_structure();
struct sve_cmd_id_regs *output;
output = (struct sve_cmd_id_regs *)sd->realm_cmd_output_buffer;
memset((void *)output, 0, sizeof(struct sve_cmd_id_regs));
realm_printf("reading ID registers: ID_AA64PFR0_EL1, "
" ID_AA64ZFR0_EL1\n");
output->id_aa64pfr0_el1 = read_id_aa64pfr0_el1();
output->id_aa64zfr0_el1 = read_id_aa64zfr0_el1();
return true;
}
/*
* Probes all VLs and return the bitmap with the bit set for each corresponding
* valid VQ. This test is called only by sve Realm
*/
bool test_realm_sve_probe_vl(void)
{
host_shared_data_t *sd = realm_get_my_shared_structure();
struct sve_cmd_probe_vl *output;
assert(is_armv8_2_sve_present());
output = (struct sve_cmd_probe_vl *)&sd->realm_cmd_output_buffer;
memset((void *)output, 0, sizeof(struct sve_cmd_probe_vl));
/* Probe all VLs */
output->vl_bitmap = sve_probe_vl(SVE_VQ_ARCH_MAX);
return true;
}
bool test_realm_sve_ops(void)
{
int val, i;
assert(is_armv8_2_sve_present());
/* get at random value to do sve_subtract */
val = rand();
for (i = 0; i < RL_SVE_OP_ARRAYSIZE; i++) {
rl_sve_op_1[i] = val - i;
rl_sve_op_2[i] = 1;
}
for (i = 0; i < SVE_TEST_ITERATIONS; i++) {
/* Config Realm with random SVE length */
sve_config_vq(SVE_GET_RANDOM_VQ);
/* Perform SVE operations, without world switch */
sve_subtract_arrays(rl_sve_op_1, rl_sve_op_1, rl_sve_op_2,
RL_SVE_OP_ARRAYSIZE);
}
/* Check result of SVE operations. */
for (i = 0; i < RL_SVE_OP_ARRAYSIZE; i++) {
if (rl_sve_op_1[i] != (val - i - SVE_TEST_ITERATIONS)) {
realm_printf("SVE ops failed\n");
return false;
}
}
return true;
}
/* Fill SVE Z registers with known pattern */
bool test_realm_sve_fill_regs(void)
{
assert(is_armv8_2_sve_present());
/* Config Realm with max SVE length */
sve_config_vq(SVE_VQ_ARCH_MAX);
sve_z_regs_write_rand(&rl_sve_z_regs_write);
sve_p_regs_write_rand(&rl_sve_p_regs_write);
sve_ffr_regs_write_rand(&rl_sve_ffr_regs_write);
/* fpcr, fpsr common registers */
fpu_cs_regs_write_rand(&rl_fpu_cs_regs_write);
return true;
}
/* Compare SVE Z registers with last filled in values */
bool test_realm_sve_cmp_regs(void)
{
bool rc = true;
uint64_t bit_map;
assert(is_armv8_2_sve_present());
memset(&rl_sve_z_regs_read, 0, sizeof(rl_sve_z_regs_read));
memset(&rl_sve_p_regs_read, 0, sizeof(rl_sve_p_regs_read));
memset(&rl_sve_ffr_regs_read, 0, sizeof(rl_sve_ffr_regs_read));
/* Read all SVE registers */
sve_z_regs_read(&rl_sve_z_regs_read);
sve_p_regs_read(&rl_sve_p_regs_read);
sve_ffr_regs_read(&rl_sve_ffr_regs_read);
/* Compare the read values with last written values */
bit_map = sve_z_regs_compare(&rl_sve_z_regs_write, &rl_sve_z_regs_read);
if (bit_map) {
rc = false;
}
bit_map = sve_p_regs_compare(&rl_sve_p_regs_write, &rl_sve_p_regs_read);
if (bit_map) {
rc = false;
}
bit_map = sve_ffr_regs_compare(&rl_sve_ffr_regs_write,
&rl_sve_ffr_regs_read);
if (bit_map) {
rc = false;
}
/* fpcr, fpsr common registers */
fpu_cs_regs_read(&rl_fpu_cs_regs_read);
if (fpu_cs_regs_compare(&rl_fpu_cs_regs_write, &rl_fpu_cs_regs_read)) {
ERROR("FPCR/FPSR mismatch\n");
rc = false;
}
return rc;
}
/* Check if Realm gets undefined abort when it accesses SVE functionality */
bool test_realm_sve_undef_abort(void)
{
realm_reset_undef_abort_count();
/* Install exception handler to catch undefined abort */
register_custom_sync_exception_handler(&realm_sync_exception_handler);
(void)sve_rdvl_1();
unregister_custom_sync_exception_handler();
return (realm_get_undef_abort_count() != 0U);
}
/* Reads and returns the ID_AA64PFR1_EL1 and ID_AA64SMFR0_EL1 registers */
bool test_realm_sme_read_id_registers(void)
{
host_shared_data_t *sd = realm_get_my_shared_structure();
struct sme_cmd_id_regs *output;
output = (struct sme_cmd_id_regs *)sd->realm_cmd_output_buffer;
memset((void *)output, 0, sizeof(struct sme_cmd_id_regs));
realm_printf("reading ID registers: ID_AA64PFR1_EL1, "
" ID_AA64SMFR0_EL1\n");
output->id_aa64pfr1_el1 = read_id_aa64pfr1_el1();
output->id_aa64smfr0_el1 = read_id_aa64smfr0_el1();
return true;
}
/* Check if Realm gets undefined abort when it access SME functionality */
bool test_realm_sme_undef_abort(void)
{
realm_reset_undef_abort_count();
/* Install exception handler to catch undefined abort */
register_custom_sync_exception_handler(&realm_sync_exception_handler);
(void)read_svcr();
unregister_custom_sync_exception_handler();
return (realm_get_undef_abort_count() != 0U);
}
/*
* Helper function to try triggering a SVE exception by executing rdvl instruction.
*/
static u_register_t read_scaled_vector_length(void)
{
u_register_t vl;
__asm__ volatile(
".arch_extension sve\n"
"rdvl %0, #1;"
".arch_extension nosve\n"
: "=r" (vl)
);
return vl;
}
/*
* Executes as many accesses to SVE registers as indicated by HOST_ARG1_INDEX.
*
* On plane exit, Plane 0 receives the following return values:
* - HOST_ARG1_INDEX: Number of attempted accesses to SVE registers.
* - HOST_ARG2_INDEX: Number of completed accesses to SVE registers.
*/
bool test_realm_sve_plane_n_access(void)
{
uint64_t iterations = realm_shared_data_get_my_host_val(HOST_ARG1_INDEX);
unsigned int n_access = 0U;
/* This should only be executed from Plane N */
assert(!realm_is_plane0());
realm_shared_data_set_my_realm_val(HOST_ARG1_INDEX, 0U);
realm_shared_data_set_my_realm_val(HOST_ARG2_INDEX, 0U);
for (; iterations > 0UL; iterations--) {
realm_shared_data_set_my_realm_val(HOST_ARG1_INDEX, ++n_access);
(void)read_scaled_vector_length();
realm_shared_data_set_my_realm_val(HOST_ARG2_INDEX, n_access);
}
return true;
}
static bool test_realm_enter_plane_n(u_register_t flags)
{
u_register_t base, plane_index, perm_index;
bool ret1;
plane_index = realm_shared_data_get_my_host_val(HOST_ARG1_INDEX);
base = realm_shared_data_get_my_host_val(HOST_ARG2_INDEX);
perm_index = plane_index + 1U;
flags &= RSI_PLANE_ENTRY_FLAG_MASK;
ret1 = plane_common_init(plane_index, perm_index, base, &run);
if (!ret1) {
return ret1;
}
realm_printf("Entering plane %ld, ep=0x%lx run=0x%lx\n", plane_index, base, &run);
return realm_plane_enter(plane_index, perm_index, flags, &run);
}
/*
* Test that combines accesses to SVE registers from Plane 0 and Plane N,
* using different sequences of entries to Plane N with and without TRAP_SIMD
* flag enabled.
*
* Expected arguments from Host:
* - HOST_ARG1_INDEX: Plane index for Pn to run.
* - HOST_ARG2_INDEX: Entry point for Pn.
* - HOST_ARG3_INDEX: If 0, first SIMD access is performed from P0.
* Otherwise, first SIMD access is performed from Pn.
*/
bool test_realm_sve_plane_n(void)
{
unsigned long attempted, success;
u_register_t plane_index = realm_shared_data_get_my_host_val(HOST_ARG1_INDEX);
if (realm_shared_data_get_my_host_val(HOST_ARG3_INDEX) == 0UL) {
/* First SIMD access performed from P0 */
INFO("Executing first SIMD access from P0\n");
(void)read_scaled_vector_length();
} else {
INFO("Executing first SIMD access from PN\n");
}
/*
* Enter Plane N with TRAP_SIMD disabled. Plane N will attempt
* to executte SVE instrucctions SIMD_TRAP_ATTEMPTS times
* and no plane exit should happen.
*/
if (!test_realm_enter_plane_n(0UL)) {
return false;
}
if ((run.exit.exit_reason != RSI_EXIT_SYNC) ||
(EC_BITS(run.exit.esr) != EC_AARCH64_HVC)) {
ERROR("Invalid exit reason from PN: 0x%lx, EC: 0x%lx\n",
run.exit.exit_reason, EC_BITS(run.exit.esr));
return false;
}
attempted = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG1_INDEX);
success = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG2_INDEX);
if ((attempted < SIMD_TRAP_ATTEMPTS) || (success < SIMD_TRAP_ATTEMPTS)) {
ERROR("Attempted %u executions of rdvl instruction. %u succedded\n",
attempted, success);
return false;
}
/* Try another access to the SVE registers from Plane 0 */
(void)read_scaled_vector_length();
/*
* Restart PN again, this time with TRAP_SIMD enabled. That should
* cause a plane exit when attempting SVE instructions.
*/
if (!test_realm_enter_plane_n(RSI_PLANE_ENTRY_FLAG_TRAP_SIMD)) {
return false;
}
if ((run.exit.exit_reason != RSI_EXIT_SYNC) ||
(EC_BITS(run.exit.esr) != EC_AARCH64_SVE)) {
ERROR("Invalid exit reason from PN: 0x%lx, EC: 0x%lx\n",
run.exit.exit_reason, EC_BITS(run.exit.esr));
return false;
}
attempted = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG1_INDEX);
success = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG2_INDEX);
/*
* Plane N should have exited after 1 attempt and the access should not
* be completed yet.
*/
if ((attempted != 1U) || (success > 0U)) {
ERROR("Attempted %u executions of rdvl instruction. %u succedded\n",
attempted, success);
return false;
}
/* Try another access to the SIMD registers from Plane 0 */
(void)read_scaled_vector_length();
/*
* Resume Plane N with TRAP_SIMD disabled. No Plane exit should happen
* due to SIMD access.
*/
if (!realm_resume_plane_n(&run, plane_index, 0U)) {
return false;
}
if ((run.exit.exit_reason != RSI_EXIT_SYNC) ||
(EC_BITS(run.exit.esr) != EC_AARCH64_HVC)) {
ERROR("Invalid exit reason from PN: 0x%lx, EC: 0x%lx\n",
run.exit.exit_reason, EC_BITS(run.exit.esr));
return false;
}
attempted = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG1_INDEX);
success = realm_shared_data_get_plane_n_val(plane_index,
REC_IDX(read_mpidr_el1()),
HOST_ARG2_INDEX);
if ((attempted < SIMD_TRAP_ATTEMPTS) || (success < SIMD_TRAP_ATTEMPTS)) {
ERROR("Attempted %u executions of rdvl instruction. %u succedded\n",
attempted, success);
return false;
}
return true;
}