blob: aac2de477a796805fad371226591557e98de6f1b [file] [log] [blame]
/*
* Copyright (c) 2020-2024, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "arch_features.h"
#include "arch_helpers.h"
#include "ffa_helpers.h"
#include "ffa_svc.h"
#include "stdint.h"
#include "utils_def.h"
#include <debug.h>
#include "ffa_helpers.h"
#include <sync.h>
#include <cactus_test_cmds.h>
#include <ffa_endpoints.h>
#include <host_realm_rmi.h>
#include <spm_common.h>
#include <spm_test_helpers.h>
#include <test_helpers.h>
#include <tftf_lib.h>
#include <xlat_tables_defs.h>
#define MAILBOX_SIZE PAGE_SIZE
#define SENDER HYP_ID
#define RECEIVER SP_ID(1)
/*
* A number of pages that is large enough that it must take two fragments to
* share.
*/
#define FRAGMENTED_SHARE_PAGE_COUNT \
(sizeof(struct ffa_memory_region) / \
sizeof(struct ffa_memory_region_constituent))
static const struct ffa_uuid expected_sp_uuids[] = {
{PRIMARY_UUID}, {SECONDARY_UUID}, {TERTIARY_UUID}
};
/* Memory section to be used for memory share operations */
static __aligned(PAGE_SIZE) uint8_t
share_page[PAGE_SIZE * FRAGMENTED_SHARE_PAGE_COUNT];
static __aligned(PAGE_SIZE) uint8_t donate_page[PAGE_SIZE];
static __aligned(PAGE_SIZE) uint8_t consecutive_donate_page[PAGE_SIZE];
static __aligned(PAGE_SIZE) uint8_t four_share_pages[PAGE_SIZE * 4];
static bool gpc_abort_triggered;
static bool check_written_words(uint32_t *ptr, uint32_t word, uint32_t wcount)
{
VERBOSE("TFTF - Memory contents after SP use:\n");
for (unsigned int i = 0U; i < wcount; i++) {
VERBOSE(" %u: %x\n", i, ptr[i]);
/* Verify content of memory is as expected. */
if (ptr[i] != word) {
return false;
}
}
return true;
}
static bool test_memory_send_expect_denied(uint32_t mem_func,
void *mem_ptr,
ffa_id_t borrower)
{
struct ffa_value ret;
struct mailbox_buffers mb;
struct ffa_memory_region_constituent constituents[] = {
{(void *)mem_ptr, 1, 0}
};
ffa_memory_handle_t handle;
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
struct ffa_memory_access receiver =
ffa_memory_access_init_permissions_from_mem_func(borrower,
mem_func);
GET_TFTF_MAILBOX(mb);
handle = memory_init_and_send((struct ffa_memory_region *)mb.send,
MAILBOX_SIZE, SENDER, &receiver, 1,
constituents, constituents_count,
mem_func, &ret);
if (handle != FFA_MEMORY_HANDLE_INVALID) {
ERROR("Received a valid FF-A memory handle, and that isn't "
"expected.\n");
return false;
}
if (!is_expected_ffa_error(ret, FFA_ERROR_DENIED)) {
return false;
}
return true;
}
static bool data_abort_handler(void)
{
uint64_t esr_elx = IS_IN_EL2() ? read_esr_el2() : read_esr_el1();
VERBOSE("%s esr_elx %llx\n", __func__, esr_elx);
if (EC_BITS(esr_elx) == EC_DABORT_CUR_EL) {
/* Synchronous data abort triggered by Granule protection */
if ((ISS_BITS(esr_elx) & ISS_DFSC_MASK) == DFSC_GPF_DABORT) {
VERBOSE("%s GPF Data Abort caught to address: %llx\n",
__func__, (uint64_t)read_far_el2());
gpc_abort_triggered = true;
return true;
}
}
return false;
}
static bool get_gpc_abort_triggered(void)
{
bool ret = gpc_abort_triggered;
gpc_abort_triggered = false;
return ret;
}
/**
* Test invocation to FF-A memory sharing interfaces that should return in an
* error.
*/
test_result_t test_share_forbidden_ranges(void)
{
const uintptr_t forbidden_address[] = {
/* Cactus SP memory. */
(uintptr_t)0x7200000,
/* SPMC Memory. */
(uintptr_t)0x6000000,
/* NS memory defined in cactus tertiary. */
(uintptr_t)0x0000880080001000,
};
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
for (unsigned i = 0; i < 3; i++) {
if (!test_memory_send_expect_denied(
FFA_MEM_SHARE_SMC64, (void *)forbidden_address[i],
RECEIVER)) {
return TEST_RESULT_FAIL;
}
}
return TEST_RESULT_SUCCESS;
}
/**
* Tests that it is possible to share memory with SWd from NWd.
* After calling the respective memory send API, it will expect a reply from
* cactus SP, at which point it will reclaim access to the memory region and
* check the memory region has been used by receiver SP.
*
* Accessing memory before a memory reclaim operation should only be possible
* in the context of a memory share operation.
* According to the FF-A spec, the owner is temporarily relinquishing
* access to the memory region on a memory lend operation, and on a
* memory donate operation the access is relinquished permanently.
* SPMC is positioned in S-EL2, and doesn't control stage-1 mapping for
* EL2. Therefore, it is impossible to enforce the expected access
* policy for a donate and lend operations within the SPMC.
* Current SPMC implementation is under the assumption of trust that
* Hypervisor (sitting in EL2) would relinquish access from EL1/EL0
* FF-A endpoint at relevant moment.
*/
static test_result_t test_memory_send_sp(uint32_t mem_func, ffa_id_t borrower,
struct ffa_memory_region_constituent *constituents,
size_t constituents_count, bool is_normal_memory)
{
struct ffa_value ret;
ffa_memory_handle_t handle;
uint32_t *ptr;
struct mailbox_buffers mb;
unsigned int rme_supported = get_armv9_2_feat_rme_support();
const bool check_gpc_fault =
mem_func != FFA_MEM_SHARE_SMC64 &&
rme_supported != 0U && is_normal_memory;
/*
* For normal memory arbitrarilty write 5 words after using memory.
* For device just write 1 so we only write in the data register of the device.
*/
const uint32_t nr_words_to_write = is_normal_memory ? 5 : 1;
struct ffa_memory_access receiver =
ffa_memory_access_init_permissions_from_mem_func(borrower,
mem_func);
/***********************************************************************
* Check if SPMC has ffa_version and expected FFA endpoints are deployed.
**********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
/*
* If the RME is enabled for the platform under test, check that the
* GPCs are working as expected, as such setup the exception handler.
*/
if (check_gpc_fault) {
register_custom_sync_exception_handler(data_abort_handler);
}
for (size_t i = 0; i < constituents_count; i++) {
VERBOSE("Sharing Address: %p\n", constituents[i].address);
ptr = (uint32_t *)constituents[i].address;
for (size_t j = 0; j < nr_words_to_write; j++) {
ptr[j] = mem_func + 0xFFA;
}
}
handle = memory_init_and_send((struct ffa_memory_region *)mb.send,
MAILBOX_SIZE, SENDER, &receiver, 1,
constituents, constituents_count,
mem_func, &ret);
if (handle == FFA_MEMORY_HANDLE_INVALID) {
return TEST_RESULT_FAIL;
}
VERBOSE("TFTF - Handle: %llx\n", handle);
ptr = (uint32_t *)constituents[0].address;
ret = cactus_mem_send_cmd(SENDER, borrower, mem_func, handle, 0,
nr_words_to_write, false, is_normal_memory);
if (!is_ffa_direct_response(ret) ||
cactus_get_response(ret) != CACTUS_SUCCESS) {
ffa_mem_reclaim(handle, 0);
ERROR("Failed memory send operation!\n");
return TEST_RESULT_FAIL;
}
/*
* If there is RME support, look to trigger an exception as soon as the
* security state is update, due to GPC fault.
*/
if (check_gpc_fault) {
*ptr = 0xBEEF;
}
if (mem_func != FFA_MEM_DONATE_SMC64) {
/* Reclaim memory entirely before checking its state. */
if (is_ffa_call_error(ffa_mem_reclaim(handle, 0))) {
tftf_testcase_printf("Couldn't reclaim memory\n");
return TEST_RESULT_FAIL;
}
for (uint32_t i = 0; i < constituents_count; i++) {
ptr = constituents[i].address;
/*
* Check that borrower used the memory as expected
* for FFA_MEM_SHARE test.
*/
if (mem_func == FFA_MEM_SHARE_SMC64 &&
!check_written_words(ptr,
mem_func + 0xFFAU,
nr_words_to_write)) {
ERROR("Fail because of state of memory.\n");
return TEST_RESULT_FAIL;
}
}
}
if (check_gpc_fault) {
unregister_custom_sync_exception_handler();
if (!get_gpc_abort_triggered()) {
ERROR("No exception due to GPC for lend/donate with RME.\n");
return TEST_RESULT_FAIL;
}
}
return TEST_RESULT_SUCCESS;
}
test_result_t test_mem_share_sp(void)
{
struct ffa_memory_region_constituent constituents[] = {
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
return test_memory_send_sp(FFA_MEM_SHARE_SMC64, RECEIVER, constituents,
constituents_count, true);
}
test_result_t test_mem_lend_sp(void)
{
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
return test_memory_send_sp(FFA_MEM_LEND_SMC64, RECEIVER, constituents,
constituents_count, true);
}
test_result_t test_mem_donate_sp(void)
{
struct ffa_memory_region_constituent constituents[] = {
{(void *)donate_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
return test_memory_send_sp(FFA_MEM_DONATE_SMC64, RECEIVER, constituents,
constituents_count, true);
}
test_result_t test_consecutive_donate(void)
{
struct ffa_memory_region_constituent constituents[] = {
{(void *)consecutive_donate_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
test_result_t ret = test_memory_send_sp(FFA_MEM_DONATE_SMC64, SP_ID(1),
constituents,
constituents_count, true);
if (ret != TEST_RESULT_SUCCESS) {
ERROR("Failed at first attempting of sharing.\n");
return TEST_RESULT_FAIL;
}
if (!test_memory_send_expect_denied(FFA_MEM_DONATE_SMC64,
consecutive_donate_page,
SP_ID(1))) {
ERROR("Memory was successfully donated again from the NWd, to "
"the same borrower.\n");
return TEST_RESULT_FAIL;
}
if (!test_memory_send_expect_denied(FFA_MEM_DONATE_SMC64,
consecutive_donate_page,
SP_ID(2))) {
ERROR("Memory was successfully donated again from the NWd, to "
"another borrower.\n");
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
/*
* Lend device memory to the Secure Partition.
*/
test_result_t test_ffa_mem_lend_device_memory_sp(void)
{
#if PLAT_fvp || PLAT_tc
struct ffa_memory_region_constituent constituents[] = {
{(void *)PLAT_ARM_UART_BASE, 1, 0},
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
return test_memory_send_sp(FFA_MEM_LEND_SMC64, RECEIVER, constituents,
constituents_count, false);
#else
return TEST_RESULT_SKIPPED;
#endif
}
/*
* Test requests a memory send operation between cactus SPs.
* Cactus SP should reply to TFTF on whether the test succeeded or not.
*/
static test_result_t test_req_mem_send_sp_to_sp(uint32_t mem_func,
ffa_id_t sender_sp,
ffa_id_t receiver_sp,
bool non_secure)
{
struct ffa_value ret;
/***********************************************************************
* Check if SPMC's ffa_version and presence of expected FF-A endpoints.
**********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
ret = cactus_req_mem_send_send_cmd(HYP_ID, sender_sp, mem_func,
receiver_sp, non_secure);
if (!is_ffa_direct_response(ret)) {
return TEST_RESULT_FAIL;
}
if (cactus_get_response(ret) == CACTUS_ERROR) {
ERROR("Failed sharing memory between SPs. Error code: %d\n",
cactus_error_code(ret));
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
/*
* Test requests a memory send operation from SP to VM.
* The tests expects cactus to reply CACTUS_ERROR, providing FF-A error code of
* the last memory send FF-A call that cactus performed.
*/
static test_result_t test_req_mem_send_sp_to_vm(uint32_t mem_func,
ffa_id_t sender_sp,
ffa_id_t receiver_vm)
{
struct ffa_value ret;
/**********************************************************************
* Check if SPMC's ffa_version and presence of expected FF-A endpoints.
*********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
ret = cactus_req_mem_send_send_cmd(HYP_ID, sender_sp, mem_func,
receiver_vm, false);
if (!is_ffa_direct_response(ret)) {
return TEST_RESULT_FAIL;
}
if (cactus_get_response(ret) == CACTUS_ERROR &&
cactus_error_code(ret) == FFA_ERROR_DENIED) {
return TEST_RESULT_SUCCESS;
}
tftf_testcase_printf("Did not get the expected error, "
"mem send returned with %d\n",
cactus_get_response(ret));
return TEST_RESULT_FAIL;
}
test_result_t test_req_mem_share_sp_to_sp(void)
{
return test_req_mem_send_sp_to_sp(FFA_MEM_SHARE_SMC64, SP_ID(3),
SP_ID(2), false);
}
test_result_t test_req_ns_mem_share_sp_to_sp(void)
{
/*
* Skip the test when RME is enabled (for test setup reasons).
* For RME tests, the model specifies 48b physical address size
* at the PE, but misses allocating RAM and increasing the PA at
* the interconnect level.
*/
if (get_armv9_2_feat_rme_support() != 0U) {
return TEST_RESULT_SKIPPED;
}
/* This test requires 48b physical address size capability. */
SKIP_TEST_IF_PA_SIZE_LESS_THAN(48);
return test_req_mem_send_sp_to_sp(FFA_MEM_SHARE_SMC64, SP_ID(3),
SP_ID(2), true);
}
test_result_t test_req_mem_lend_sp_to_sp(void)
{
return test_req_mem_send_sp_to_sp(FFA_MEM_LEND_SMC64, SP_ID(3),
SP_ID(2), false);
}
test_result_t test_req_mem_donate_sp_to_sp(void)
{
return test_req_mem_send_sp_to_sp(FFA_MEM_DONATE_SMC64, SP_ID(1),
SP_ID(3), false);
}
test_result_t test_req_mem_share_sp_to_vm(void)
{
return test_req_mem_send_sp_to_vm(FFA_MEM_SHARE_SMC64, SP_ID(1),
HYP_ID);
}
test_result_t test_req_mem_lend_sp_to_vm(void)
{
return test_req_mem_send_sp_to_vm(FFA_MEM_LEND_SMC64, SP_ID(2),
HYP_ID);
}
test_result_t test_mem_share_to_sp_clear_memory(void)
{
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
struct mailbox_buffers mb;
uint32_t remaining_constituent_count;
uint32_t total_length;
uint32_t fragment_length;
ffa_memory_handle_t handle;
struct ffa_value ret;
/* Arbitrarily write 10 words after using shared memory. */
const uint32_t nr_words_to_write = 10U;
struct ffa_memory_access receiver =
ffa_memory_access_init_permissions_from_mem_func(
RECEIVER, FFA_MEM_LEND_SMC64);
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, MAILBOX_SIZE, SENDER,
&receiver, 1, constituents, constituents_count, 0,
FFA_MEMORY_REGION_FLAG_CLEAR,
FFA_MEMORY_NOT_SPECIFIED_MEM, 0, 0,
&total_length, &fragment_length);
if (remaining_constituent_count != 0) {
ERROR("Transaction descriptor initialization failed!\n");
return TEST_RESULT_FAIL;
}
handle = memory_send(mb.send, FFA_MEM_LEND_SMC64, constituents,
constituents_count, remaining_constituent_count,
fragment_length, total_length, &ret);
if (handle == FFA_MEMORY_HANDLE_INVALID) {
ERROR("Memory Share failed!\n");
return TEST_RESULT_FAIL;
}
VERBOSE("Memory has been shared!\n");
ret = cactus_mem_send_cmd(SENDER, RECEIVER, FFA_MEM_LEND_SMC64, handle,
FFA_MEMORY_REGION_FLAG_CLEAR,
nr_words_to_write, false, true);
if (!is_ffa_direct_response(ret)) {
return TEST_RESULT_FAIL;
}
if (cactus_get_response(ret) != CACTUS_SUCCESS) {
ERROR("Failed memory send operation!\n");
return TEST_RESULT_FAIL;
}
ret = ffa_mem_reclaim(handle, 0);
if (is_ffa_call_error(ret)) {
ERROR("Memory reclaim failed!\n");
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
/**
* Print `region` if LOG_LEVEL >= LOG_LEVEL_VERBOSE
*/
static void print_memory_region(struct ffa_memory_region *region)
{
VERBOSE("region.sender = %d\n", region->sender);
VERBOSE("region.attributes.shareability = %d\n",
region->attributes.shareability);
VERBOSE("region.attributes.cacheability = %d\n",
region->attributes.cacheability);
VERBOSE("region.attributes.type = %d\n", region->attributes.type);
VERBOSE("region.attributes.security = %d\n",
region->attributes.security);
VERBOSE("region.flags = %d\n", region->flags);
VERBOSE("region.handle = %lld\n", region->handle);
VERBOSE("region.tag = %lld\n", region->tag);
VERBOSE("region.memory_access_desc_size = %d\n",
region->memory_access_desc_size);
VERBOSE("region.receiver_count = %d\n", region->receiver_count);
VERBOSE("region.receivers_offset = %d\n", region->receivers_offset);
}
/**
* Used by hypervisor retrieve request test: validate descriptors provided by
* SPMC.
*/
static bool verify_retrieve_response(const struct ffa_memory_region *region1,
const struct ffa_memory_region *region2)
{
if (region1->sender != region2->sender) {
ERROR("region1.sender=%d, expected %d\n", region1->sender,
region2->sender);
return false;
}
if (region1->attributes.shareability != region2->attributes.shareability) {
ERROR("region1.attributes.shareability=%d, expected %d\n",
region1->attributes.shareability,
region2->attributes.shareability);
return false;
}
if (region1->attributes.cacheability != region2->attributes.cacheability) {
ERROR("region1.attributes.cacheability=%d, expected %d\n",
region1->attributes.cacheability,
region2->attributes.cacheability);
return false;
}
if (region1->attributes.type != region2->attributes.type) {
ERROR("region1.attributes.type=%d, expected %d\n",
region1->attributes.type, region2->attributes.type);
return false;
}
if (region1->attributes.security != region2->attributes.security) {
ERROR("region1.attributes.security=%d, expected %d\n",
region1->attributes.security, region2->attributes.security);
return false;
}
if (region1->flags != region2->flags) {
ERROR("region1->flags=%d, expected %d\n", region1->flags,
region2->flags);
return false;
}
if (region1->handle != region2->handle) {
ERROR("region1.handle=%lld, expected %lld\n", region1->handle,
region2->handle);
return false;
}
if (region1->tag != region2->tag) {
ERROR("region1.tag=%lld, expected %lld\n", region1->tag, region2->tag);
return false;
}
if (region1->memory_access_desc_size != region2->memory_access_desc_size) {
ERROR("region1.memory_access_desc_size=%d, expected %d\n",
region1->memory_access_desc_size,
region2->memory_access_desc_size);
return false;
}
if (region1->receiver_count != region2->receiver_count) {
ERROR("region1.receiver_count=%d, expected %d\n",
region1->receiver_count, region2->receiver_count);
return false;
}
if (region1->receivers_offset != region2->receivers_offset) {
ERROR("region1.receivers_offset=%d, expected %d\n",
region1->receivers_offset, region2->receivers_offset);
return false;
}
for (uint32_t i = 0; i < 3; i++) {
if (region1->reserved[i] != 0) {
ERROR("region.reserved[%d]=%d, expected 0\n", i,
region1->reserved[i]);
return false;
}
}
return true;
}
/**
* Used by hypervisor retrieve request test: validate descriptors provided by
* SPMC.
*/
static bool
verify_constituent(struct ffa_memory_region_constituent *constituent,
void *address, uint32_t page_count)
{
if (constituent->address != address) {
ERROR("constituent.address=%p, expected %p\n",
constituent->address, address);
return false;
}
if (constituent->page_count != page_count) {
ERROR("constituent.page_count=%d, expected %d\n",
constituent->page_count, page_count);
return false;
}
if (constituent->reserved != 0) {
ERROR("constituent.reserved=%d, expected 0\n",
constituent->reserved);
return false;
}
return true;
}
/**
* Used by hypervisor retrieve request test: validate descriptors provided by
* SPMC.
*/
static bool verify_composite(struct ffa_composite_memory_region *composite,
struct ffa_memory_region_constituent *constituent,
uint32_t page_count, uint32_t constituent_count)
{
if (composite->page_count != page_count) {
ERROR("composite.page_count=%d, expected %d\n",
composite->page_count, page_count);
return false;
}
if (composite->constituent_count != constituent_count) {
ERROR("composite.constituent_count=%d, expected %d\n",
composite->constituent_count, constituent_count);
return false;
}
if (composite->reserved_0 != 0) {
ERROR("composite.reserved_0=%llu, expected 0\n",
composite->reserved_0);
return false;
}
for (uint32_t j = 0; j < composite->constituent_count; j++) {
if (!verify_constituent(constituent, share_page, 1)) {
return false;
}
}
return true;
}
static bool verify_receivers_impdef(struct ffa_memory_access_impdef impdef1,
struct ffa_memory_access_impdef impdef2)
{
if (impdef1.val[0] != impdef2.val[0] ||
impdef1.val[1] != impdef2.val[1]) {
ERROR("ipmdef1.val[0]=%llu expected=%llu"
" ipmdef1.val[1]=%llu expected=%llu\n",
impdef1.val[0], impdef2.val[0],
impdef1.val[1], impdef2.val[1]);
return false;
}
return true;
}
static bool verify_permissions(
ffa_memory_access_permissions_t permissions1,
ffa_memory_access_permissions_t permissions2)
{
uint8_t access1;
uint8_t access2;
access1 = permissions1.data_access;
access2 = permissions2.data_access;
if (access1 != access2) {
ERROR("permissions1.data_access=%u expected=%u\n",
access1, access2);
return false;
}
access1 = permissions1.instruction_access;
access2 = permissions2.instruction_access;
if (access1 != access2) {
ERROR("permissions1.instruction_access=%u expected=%u\n",
access1, access2);
return false;
}
return true;
}
/**
* Used by hypervisor retrieve request test: validate descriptors provided by
* SPMC.
*/
static bool verify_receivers(struct ffa_memory_access *receivers1,
struct ffa_memory_access *receivers2,
uint32_t receivers_count)
{
for (uint32_t i = 0; i < receivers_count; i++) {
if (receivers1[i].receiver_permissions.receiver !=
receivers2[i].receiver_permissions.receiver) {
ERROR("receivers1[%u].receiver_permissions.receiver=%x"
" expected=%x\n", i,
receivers1[i].receiver_permissions.receiver,
receivers2[i].receiver_permissions.receiver);
return false;
}
if (receivers1[i].receiver_permissions.flags !=
receivers2[i].receiver_permissions.flags) {
ERROR("receivers1[%u].receiver_permissions.flags=%u"
" expected=%u\n", i,
receivers1[i].receiver_permissions.flags,
receivers2[i].receiver_permissions.flags);
return false;
}
if (!verify_permissions(
receivers1[i].receiver_permissions.permissions,
receivers2[i].receiver_permissions.permissions)) {
return false;
}
if (receivers1[i].composite_memory_region_offset !=
receivers2[i].composite_memory_region_offset) {
ERROR("receivers1[%u].composite_memory_region_offset=%u"
" expected %u\n",
i, receivers1[i].composite_memory_region_offset,
receivers2[i].composite_memory_region_offset);
return false;
}
if (!verify_receivers_impdef(receivers1[i].impdef,
receivers1[i].impdef)) {
return false;
}
}
return true;
}
/**
* Helper for performing a hypervisor retrieve request test.
*/
static test_result_t hypervisor_retrieve_request_test_helper(
uint32_t mem_func, bool multiple_receivers, bool fragmented)
{
static struct ffa_memory_region_constituent
sent_constituents[FRAGMENTED_SHARE_PAGE_COUNT];
__aligned(PAGE_SIZE) static uint8_t page[PAGE_SIZE * 2] = {0};
struct ffa_memory_region *hypervisor_retrieve_response =
(struct ffa_memory_region *)page;
struct ffa_memory_region expected_response;
struct mailbox_buffers mb;
ffa_memory_handle_t handle;
struct ffa_value ret;
struct ffa_composite_memory_region *composite;
struct ffa_memory_access *retrvd_receivers;
uint32_t expected_flags = 0;
/*
* Do not expect the security state to be reported given that it has no
* meaning in the normal world.
* Given the hypervisor retrieve request should only be used for memory
* shared/lent/donated from the NWd, hence all memory is non_secure.
*/
ffa_memory_attributes_t expected_attrs = {
.cacheability = FFA_MEMORY_CACHE_WRITE_BACK,
.shareability = FFA_MEMORY_INNER_SHAREABLE,
.security = FFA_MEMORY_SECURITY_UNSPECIFIED,
.type = (!multiple_receivers && mem_func != FFA_MEM_SHARE_SMC64)
? FFA_MEMORY_NOT_SPECIFIED_MEM
: FFA_MEMORY_NORMAL_MEM,
};
struct ffa_memory_access receivers[2] = {
ffa_memory_access_init_permissions_from_mem_func(SP_ID(1),
mem_func),
ffa_memory_access_init_permissions_from_mem_func(SP_ID(2),
mem_func),
};
/*
* Only pass 1 receiver to `memory_init_and_send` if we are not testing
* the multiple-receivers functionality of the hypervisor retrieve
* request.
*/
uint32_t receiver_count =
multiple_receivers ? ARRAY_SIZE(receivers) : 1;
uint32_t sent_constituents_count =
fragmented ? ARRAY_SIZE(sent_constituents) : 1;
/* Prepare the composite offset for the comparison. */
for (uint32_t i = 0; i < receiver_count; i++) {
receivers[i].composite_memory_region_offset =
sizeof(struct ffa_memory_region) +
receiver_count *
sizeof(struct ffa_memory_access);
}
/* Add a page per constituent, so that we exhaust the size of a single
* fragment (for testing). In a real world scenario, the whole region
* could be described in a single constituent.
*/
for (uint32_t i = 0; i < sent_constituents_count; i++) {
sent_constituents[i].address = share_page + i * PAGE_SIZE;
sent_constituents[i].page_count = 1;
sent_constituents[i].reserved = 0;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
switch (mem_func) {
case FFA_MEM_SHARE_SMC64:
expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_SHARE;
break;
case FFA_MEM_LEND_SMC64:
expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_LEND;
break;
case FFA_MEM_DONATE_SMC64:
expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_DONATE;
break;
default:
ERROR("Invalid mem_func: %d\n", mem_func);
panic();
}
handle = memory_init_and_send(mb.send, MAILBOX_SIZE, SENDER, receivers,
receiver_count, sent_constituents,
sent_constituents_count, mem_func, &ret);
if (handle == FFA_MEMORY_HANDLE_INVALID) {
ERROR("Memory share failed: %d\n", ffa_error_code(ret));
return TEST_RESULT_FAIL;
}
/*
* Send Hypervisor Retrieve request according to section 17.4.3 of FFA
* v1.2-REL0 specification.
*/
if (!hypervisor_retrieve_request(&mb, handle, page, sizeof(page))) {
return TEST_RESULT_FAIL;
}
print_memory_region(hypervisor_retrieve_response);
/*
* Verify the received `FFA_MEM_RETRIEVE_RESP` aligns with
* transaction description sent above.
*/
expected_response = (struct ffa_memory_region) {
.sender = SENDER,
.attributes = expected_attrs,
.flags = expected_flags,
.handle = handle,
.tag = 0,
.memory_access_desc_size = sizeof(struct ffa_memory_access),
.receiver_count = receiver_count,
.receivers_offset =
offsetof(struct ffa_memory_region, receivers),
};
if (!verify_retrieve_response(hypervisor_retrieve_response,
&expected_response)) {
return TEST_RESULT_FAIL;
}
retrvd_receivers =
ffa_memory_region_get_receiver(hypervisor_retrieve_response, 0);
if (!verify_receivers(retrvd_receivers,
receivers, receiver_count)) {
return TEST_RESULT_FAIL;
}
composite = ffa_memory_region_get_composite(
hypervisor_retrieve_response, 0);
if (!verify_composite(composite, composite->constituents,
sent_constituents_count, sent_constituents_count)) {
return TEST_RESULT_FAIL;
}
/*
* Reclaim for the SPMC to deallocate any data related to the handle.
*/
ret = ffa_mem_reclaim(handle, 0);
if (is_ffa_call_error(ret)) {
ERROR("Memory reclaim failed: %d\n", ffa_error_code(ret));
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
test_result_t test_hypervisor_share_retrieve(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_SHARE_SMC64, false, false);
}
test_result_t test_hypervisor_lend_retrieve(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_LEND_SMC64, false, false);
}
test_result_t test_hypervisor_donate_retrieve(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_DONATE_SMC64, false, false);
}
test_result_t test_hypervisor_share_retrieve_multiple_receivers(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_SHARE_SMC64, true, false);
}
test_result_t test_hypervisor_lend_retrieve_multiple_receivers(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_LEND_SMC64, true, false);
}
test_result_t test_hypervisor_share_retrieve_fragmented(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_SHARE_SMC64, false, true);
}
test_result_t test_hypervisor_lend_retrieve_fragmented(void)
{
return hypervisor_retrieve_request_test_helper(FFA_MEM_LEND_SMC64, false, true);
}
/**
* Test helper that performs memory sharing operation, and alters the PAS
* of the memory, to validate that SPM intersects the operation in case the PAS
* is not coherent with its use. Relevant for the functioning of FFA_MEM_LEND
* and FFA_MEM_DONATE from NWd to an SP.
*
* In cases the memory is not in NS state, the SPMC should intersect memory
* management call with an appropriate FFA_ERROR.
*/
static test_result_t test_ffa_mem_send_realm_expect_fail(
uint32_t mem_func, ffa_id_t borrower,
struct ffa_memory_region_constituent *constituents,
size_t constituents_count, uint64_t delegate_addr)
{
struct ffa_value ret;
uint32_t remaining_constituent_count;
uint32_t total_length;
uint32_t fragment_length;
struct mailbox_buffers mb;
u_register_t ret_rmm;
test_result_t result = TEST_RESULT_FAIL;
struct ffa_memory_access receiver =
ffa_memory_access_init_permissions_from_mem_func(borrower,
mem_func);
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
/***********************************************************************
* Check if SPMC has ffa_version and expected FFA endpoints are deployed.
**********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
register_custom_sync_exception_handler(data_abort_handler);
/*
* Delegate page to a realm. This should make memory sharing operation
* fail.
*/
ret_rmm = host_rmi_granule_delegate((u_register_t)delegate_addr);
if (ret_rmm != 0UL) {
INFO("Delegate operation returns 0x%lx for address %llx\n",
ret_rmm, delegate_addr);
return TEST_RESULT_FAIL;
}
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, MAILBOX_SIZE, SENDER,
&receiver, 1, constituents, constituents_count, 0,
FFA_MEMORY_REGION_FLAG_CLEAR,
FFA_MEMORY_NOT_SPECIFIED_MEM, 0, 0,
&total_length, &fragment_length);
if (remaining_constituent_count != 0) {
goto out;
}
switch (mem_func) {
case FFA_MEM_LEND_SMC64:
ret = ffa_mem_lend(total_length, fragment_length);
break;
case FFA_MEM_DONATE_SMC64:
ret = ffa_mem_donate(total_length, fragment_length);
break;
default:
ERROR("Not expected for func name: %x\n", mem_func);
return TEST_RESULT_FAIL;
}
if (!is_expected_ffa_error(ret, FFA_ERROR_DENIED)) {
goto out;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)delegate_addr);
for (uint32_t i = 0; i < constituents_count; i++) {
uint32_t *ptr = (uint32_t *)constituents[i].address;
*ptr = 0xFFA;
}
if (get_gpc_abort_triggered()) {
ERROR("Exception due to GPC for lend/donate with RME. Not"
" expected for this case.\n");
result = TEST_RESULT_FAIL;
} else {
result = TEST_RESULT_SUCCESS;
}
out:
unregister_custom_sync_exception_handler();
if (ret_rmm != 0UL) {
INFO("Undelegate operation returns 0x%lx for address %llx\n",
ret_rmm, (uint64_t)delegate_addr);
return TEST_RESULT_FAIL;
}
return result;
}
/**
* Memory to be shared between partitions is described in a composite, with
* various constituents. In an RME system, the memory must be in NS PAS in
* operations from NWd to an SP. In case the PAS is not following this
* expectation memory lend/donate should fail, and all constituents must
* remain in the NS PAS.
*
* This test validates that if one page in the middle of one of the constituents
* is not in the NS PAS the operation fails.
*/
test_result_t test_ffa_mem_send_sp_realm_memory(void)
{
test_result_t ret;
uint32_t mem_func[] = {FFA_MEM_LEND_SMC64, FFA_MEM_DONATE_SMC64};
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
/***********************************************************************
* Check if SPMC has ffa_version and expected FFA endpoints are deployed.
**********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
for (unsigned j = 0; j < ARRAY_SIZE(mem_func); j++) {
for (unsigned int i = 0; i < 4; i++) {
/* Address to be delegated to Realm PAS. */
uint64_t realm_addr =
(uint64_t)&four_share_pages[i * PAGE_SIZE];
INFO("%s memory with realm addr: %llx\n",
mem_func[j] == FFA_MEM_LEND_SMC64
? "Lend"
: "Donate",
realm_addr);
ret = test_ffa_mem_send_realm_expect_fail(
mem_func[j], SP_ID(1), constituents,
constituents_count, realm_addr);
if (ret != TEST_RESULT_SUCCESS) {
break;
}
}
}
return ret;
}
/**
* Memory to be shared between partitions is described in a composite, with
* various constituents. In an RME system, the memory must be in NS PAS in
* operations from NWd to an SP. In case the PAS is not following this
* expectation memory lend/donate should fail, and all constituents must
* remain in the NS PAS.
*
* This test validates the case in which the memory lend/donate fail in
* case one of the constituents in the composite is not in the NS PAS.
*/
test_result_t test_ffa_mem_lend_sp_realm_memory_separate_constituent(void)
{
test_result_t ret;
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = sizeof(constituents) /
sizeof(struct ffa_memory_region_constituent);
/* Address to be delegated to Realm PAS. */
uint64_t realm_addr = (uint64_t)&share_page[0];
INFO("Sharing memory with realm addr: %llx\n", realm_addr);
ret = test_ffa_mem_send_realm_expect_fail(
FFA_MEM_LEND_SMC64, SP_ID(1), constituents,
constituents_count, realm_addr);
return ret;
}
/**
* Map the NS RXTX buffers to the SPM, change RX buffer PAS to realm,
* invoke the FFA_MEM_SHARE interface, such that SPM does NS access
* to realm region and triggers GPF.
*/
test_result_t test_ffa_mem_share_tx_realm_expect_fail(void)
{
struct ffa_value ret;
uint32_t total_length;
uint32_t fragment_length;
struct mailbox_buffers mb;
u_register_t ret_rmm;
struct ffa_memory_access receiver =
ffa_memory_access_init_permissions_from_mem_func(SP_ID(1),
FFA_MEM_SHARE_SMC64);
size_t remaining_constituent_count;
struct ffa_memory_region_constituent constituents[] = {
{(void *)share_page, 1, 0}
};
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
/***********************************************************************
* Check if SPMC has ffa_version and expected FFA endpoints are deployed.
**********************************************************************/
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, PAGE_SIZE, HYP_ID,
&receiver, 1, constituents, 1, 0, 0,
FFA_MEMORY_NOT_SPECIFIED_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE,
&total_length, &fragment_length);
if (remaining_constituent_count != 0) {
return TEST_RESULT_FAIL;
}
/*
* Delegate TX buffer to realm.
*/
ret_rmm = host_rmi_granule_delegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
INFO("Delegate operation returns %#lx for address %p\n",
ret_rmm, mb.send);
return TEST_RESULT_FAIL;
}
ret = ffa_mem_share(total_length, fragment_length);
/* Access to Realm region from SPMC should return FFA_ERROR_ABORTED. */
if (!is_expected_ffa_error(ret, FFA_ERROR_ABORTED)) {
return TEST_RESULT_FAIL;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
INFO("Undelegate operation returns 0x%lx for address %p\n",
ret_rmm, mb.send);
return TEST_RESULT_FAIL;
}
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, PAGE_SIZE, HYP_ID,
&receiver, 1, constituents, 1, 0, 0,
FFA_MEMORY_NOT_SPECIFIED_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE,
&total_length, &fragment_length);
/* Retry but expect test to pass. */
ret = ffa_mem_share(total_length, fragment_length);
if (is_ffa_call_error(ret)) {
return TEST_RESULT_FAIL;
}
/* Reclaim to clean-up. */
ret = ffa_mem_reclaim(ffa_mem_success_handle(ret), 0);
if (is_ffa_call_error(ret)) {
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
/**
* Base helper to prepare for tests that need to retrieve memory from the SPMC
* from a VM endpoint.
*/
static ffa_memory_handle_t base_memory_send_for_nwd_retrieve(struct mailbox_buffers *mb,
struct ffa_memory_access receivers[],
size_t receivers_count)
{
ffa_memory_handle_t handle;
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
const uint32_t constituents_count = ARRAY_SIZE(constituents);
struct ffa_value ret;
/* Prepare the composite offset for the comparison. */
for (uint32_t i = 0; i < receivers_count; i++) {
receivers[i].composite_memory_region_offset =
sizeof(struct ffa_memory_region) +
receivers_count *
sizeof(struct ffa_memory_access);
}
handle = memory_init_and_send(mb->send, MAILBOX_SIZE, SENDER, receivers,
receivers_count, constituents,
constituents_count, FFA_MEM_SHARE_SMC64, &ret);
return handle;
}
/**
* Test FF-A memory retrieve request from a VM into the SPMC.
* TFTF invokes all the FF-A calls expected from an hypervisor into the
* SPMC, even those that would be initiated by a VM, and then forwarded
* to the SPMC by the Hypervisor.
*/
test_result_t test_ffa_memory_retrieve_request_from_vm(void)
{
struct mailbox_buffers mb;
struct ffa_memory_region *m;
struct ffa_memory_access receivers[2] = {
ffa_memory_access_init_permissions_from_mem_func(VM_ID(1),
FFA_MEM_SHARE_SMC64),
ffa_memory_access_init_permissions_from_mem_func(SP_ID(2),
FFA_MEM_SHARE_SMC64),
};
ffa_memory_handle_t handle;
GET_TFTF_MAILBOX(mb);
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
handle = base_memory_send_for_nwd_retrieve(&mb, receivers, ARRAY_SIZE(receivers));
if (handle == FFA_MEMORY_HANDLE_INVALID) {
return TEST_RESULT_FAIL;
}
if (!memory_retrieve(&mb, &m, handle, 0, receivers, ARRAY_SIZE(receivers),
0, true)) {
ERROR("Failed to retrieve the memory.\n");
return TEST_RESULT_FAIL;
}
ffa_rx_release();
if (!memory_relinquish(mb.send, handle, VM_ID(1))) {
ERROR("%s: Failed to relinquish.\n", __func__);
return TEST_RESULT_FAIL;
}
if (is_ffa_call_error(ffa_mem_reclaim(handle, 0))) {
ERROR("%s: Failed to reclaim memory.\n", __func__);
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
test_result_t base_ffa_memory_retrieve_request_fail_buffer_realm(bool delegate_rx,
bool is_hypervisor_retrieve_req)
{
struct mailbox_buffers mb;
struct ffa_memory_access receivers[2] = {
ffa_memory_access_init_permissions_from_mem_func(VM_ID(1),
FFA_MEM_SHARE_SMC64),
ffa_memory_access_init_permissions_from_mem_func(SP_ID(2),
FFA_MEM_SHARE_SMC64),
};
ffa_memory_handle_t handle;
u_register_t ret_rmm;
struct ffa_value ret;
size_t descriptor_size;
void *to_delegate;
GET_TFTF_MAILBOX(mb);
to_delegate = delegate_rx ? mb.recv : mb.send;
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
handle = base_memory_send_for_nwd_retrieve(&mb, receivers, ARRAY_SIZE(receivers));
if (handle == FFA_MEMORY_HANDLE_INVALID) {
return TEST_RESULT_FAIL;
}
if (is_hypervisor_retrieve_req) {
/* Prepare the hypervisor retrieve request. */
ffa_hypervisor_retrieve_request_init(mb.send, handle);
descriptor_size = sizeof(struct ffa_memory_region);
} else {
/* Prepare the descriptor before delegating the buffer. */
descriptor_size = ffa_memory_retrieve_request_init(
mb.send, handle, SENDER, receivers, ARRAY_SIZE(receivers),
0, 0, FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE);
}
/* Delegate buffer to realm. */
ret_rmm = host_rmi_granule_delegate((u_register_t)to_delegate);
if (ret_rmm != 0UL) {
ERROR("Delegate operation returns %#lx for address %p\n",
ret_rmm, mb.send);
return TEST_RESULT_FAIL;
}
ret = ffa_mem_retrieve_req(descriptor_size, descriptor_size);
if (!is_expected_ffa_error(ret, FFA_ERROR_ABORTED)) {
return TEST_RESULT_FAIL;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)to_delegate);
if (ret_rmm != 0UL) {
ERROR("Undelegate operation returns %#lx for address %p\n",
ret_rmm, mb.send);
return TEST_RESULT_FAIL;
}
if (is_hypervisor_retrieve_req) {
/* Prepare the hypervisor retrieve request. */
ffa_hypervisor_retrieve_request_init(mb.send, handle);
descriptor_size = sizeof(struct ffa_memory_region);
} else {
/* Prepare the descriptor before delegating the buffer. */
descriptor_size = ffa_memory_retrieve_request_init(
mb.send, handle, SENDER, receivers, ARRAY_SIZE(receivers),
0, 0, FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE);
}
/* Retry the memory retrieve request, but this time expect success. */
ret = ffa_mem_retrieve_req(descriptor_size, descriptor_size);
if (is_ffa_call_error(ret)) {
return TEST_RESULT_FAIL;
}
ffa_rx_release();
if (!is_hypervisor_retrieve_req &&
!memory_relinquish(mb.send, handle, VM_ID(1))) {
ERROR("%s: Failed to relinquish.\n", __func__);
return TEST_RESULT_FAIL;
}
if (is_ffa_call_error(ffa_mem_reclaim(handle, 0))) {
ERROR("%s: Failed to reclaim memory.\n", __func__);
return TEST_RESULT_FAIL;
}
return TEST_RESULT_SUCCESS;
}
/**
* Test that a retrieve request from the hypervisor would fail if the TX buffer
* was in realm state. This is recreating the situation in which the Hyp doesn't
* track the state of the operation, and it is forwarding the retrieve request
* to the SPMC.
*/
test_result_t test_ffa_memory_retrieve_request_fail_tx_realm(void)
{
return base_ffa_memory_retrieve_request_fail_buffer_realm(false, false);
}
/**
* Test that a retrieve request from the hypervisor would fail if the RX buffer
* was in realm state. This is recreating the situation in which the Hyp doesn't
* track the state of the operation, and it is forwarding the retrieve request
* to the SPMC. The operation shall fail at the point at which the SPMC is
* providing retrieve response. The SPMC should have reverted the change to any
* of its share state tracking structures, such that the final reclaim would be
* possible.
*/
test_result_t test_ffa_memory_retrieve_request_fail_rx_realm(void)
{
return base_ffa_memory_retrieve_request_fail_buffer_realm(true, false);
}
/**
* Test that a memory relinquish call fails smoothly if the TX buffer of the
* Hypervisor is on realm PAS.
*/
test_result_t test_ffa_memory_relinquish_fail_tx_realm(void)
{
struct mailbox_buffers mb;
struct ffa_memory_region *m;
const ffa_id_t vm_id = VM_ID(1);
struct ffa_memory_access receivers[2] = {
ffa_memory_access_init_permissions_from_mem_func(vm_id,
FFA_MEM_SHARE_SMC64),
ffa_memory_access_init_permissions_from_mem_func(SP_ID(2),
FFA_MEM_SHARE_SMC64),
};
struct ffa_value ret;
ffa_memory_handle_t handle;
u_register_t ret_rmm;
GET_TFTF_MAILBOX(mb);
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
handle = base_memory_send_for_nwd_retrieve(&mb, receivers, ARRAY_SIZE(receivers));
if (handle == FFA_MEMORY_HANDLE_INVALID) {
return TEST_RESULT_FAIL;
}
if (!memory_retrieve(&mb, &m, handle, 0, receivers, ARRAY_SIZE(receivers),
0, true)) {
ERROR("Failed to retrieve the memory.\n");
return TEST_RESULT_FAIL;
}
/* Prepare relinquish descriptor before calling ffa_mem_relinquish. */
ffa_mem_relinquish_init(mb.send, handle, 0, vm_id);
/*
* Delegate page to a realm. This should make memory sharing operation
* fail.
*/
ret_rmm = host_rmi_granule_delegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
ERROR("Delegate operation returns 0x%lx for address %llx\n",
ret_rmm, (uint64_t)mb.send);
return TEST_RESULT_FAIL;
}
/* Access to Realm region from SPMC should return FFA_ERROR_ABORTED. */
ret = ffa_mem_relinquish();
if (!is_expected_ffa_error(ret, FFA_ERROR_ABORTED)) {
return TEST_RESULT_FAIL;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
ERROR("Undelegate operation returns 0x%lx for address %llx\n",
ret_rmm, (uint64_t)mb.send);
return TEST_RESULT_FAIL;
}
/* Prepare the descriptor. */
ffa_mem_relinquish_init(mb.send, handle, 0, vm_id);
/* After undelegate the relinquish is expected to succeed. */
ret = ffa_mem_relinquish();
if (is_ffa_call_error(ret)) {
ERROR("Expected relinquish to succeed\n");
return TEST_RESULT_FAIL;
}
ret = ffa_mem_reclaim(handle, 0);
if (is_ffa_call_error(ret)) {
ERROR("Memory reclaim failed!\n");
return TEST_RESULT_FAIL;
}
ffa_rx_release();
return TEST_RESULT_SUCCESS;
}
/**
* Test that a hypervisor retrieve request would fail if the TX buffer
* was in realm PAS.
* The hypervisor retrieve request normally happens during an FFA_MEM_RECLAIM.
* This validates that the SPMC is able to recover from a GPF from accessing the
* TX buffer when reading the hypervisor retrieve request message.
*/
test_result_t test_ffa_hypervisor_retrieve_request_fail_tx_realm(void)
{
return base_ffa_memory_retrieve_request_fail_buffer_realm(false, true);
}
/**
* Test that a hypervisor retrieve request would fail if the RX buffer
* was in realm PAS.
* The hypervisor retrieve request normally happens during an FFA_MEM_RECLAIM.
* This validates the SPMC is able to recover from a GPF from accessing the RX
* buffer when preparing the retrieve response.
*/
test_result_t test_ffa_hypervisor_retrieve_request_fail_rx_realm(void)
{
return base_ffa_memory_retrieve_request_fail_buffer_realm(true, true);
}
/**
* Do a memory sharing operation over two fragments.
* Before the 2nd fragment the TX buffer is set in the realm PAS.
* The SPMC should fault, recover from it and return ffa_error(FFA_ERROR_ABORTED).
*/
test_result_t test_ffa_memory_share_fragmented_tx_realm(void)
{
struct mailbox_buffers mb;
uint32_t remaining_constituent_count = 0;
uint32_t total_length;
uint32_t fragment_length;
struct ffa_memory_access receiver = ffa_memory_access_init_permissions_from_mem_func(
SP_ID(1), FFA_MEM_SHARE_SMC32);
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
struct ffa_value ffa_ret;
u_register_t ret_rmm;
test_result_t ret;
uint64_t handle;
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
register_custom_sync_exception_handler(data_abort_handler);
/* Only send one constituent to start with. */
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, MAILBOX_SIZE, SENDER,
&receiver, 1, constituents, ARRAY_SIZE(constituents), 0,
0, FFA_MEMORY_NOT_SPECIFIED_MEM,
FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE,
&total_length, &fragment_length);
/* It should have copied them all. */
if (remaining_constituent_count > 0) {
ERROR("Transaction descriptor initialization failed!\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/*
* Take the size of a constituent from the fragment to force the
* operation to be fragmented.
*/
fragment_length -= sizeof(struct ffa_memory_region_constituent);
ffa_ret = ffa_mem_share(total_length, fragment_length);
if (!is_expected_ffa_return(ffa_ret, FFA_MEM_FRAG_RX)) {
ERROR("Expected %s after the memory share.\n",
ffa_func_name(FFA_MEM_FRAG_RX));
ret = TEST_RESULT_FAIL;
goto exit;
}
handle = ffa_frag_handle(ffa_ret);
if (handle == FFA_MEMORY_HANDLE_INVALID) {
ERROR("SPMC returned an invalid handle for the operation.\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Prepare the next fragment for the operation. */
remaining_constituent_count = ffa_memory_fragment_init(
mb.send, PAGE_SIZE, &constituents[1], 1, &fragment_length);
/*
* Delegate send/tx buffer to a realm. This should make memory sharing operation
* fail.
*/
ret_rmm = host_rmi_granule_delegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
INFO("Delegate operation returns 0x%lx for address %p\n",
ret_rmm, mb.send);
ret = TEST_RESULT_FAIL;
goto exit;
}
ffa_ret = ffa_mem_frag_tx(handle, fragment_length);
if (!is_expected_ffa_error(ffa_ret, FFA_ERROR_ABORTED)) {
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)mb.send);
if (ret_rmm != 0UL) {
ERROR("Undelegate operation returns 0x%lx for address %llx\n",
ret_rmm, (uint64_t)mb.send);
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Undelegate operation scrubbed the TX buffer, re-init the fragment. */
remaining_constituent_count = ffa_memory_fragment_init(
mb.send, PAGE_SIZE, &constituents[1], 1, &fragment_length);
/* This time the test is expected to pass. */
ffa_ret = ffa_mem_frag_tx(handle, fragment_length);
if (is_ffa_call_error(ffa_ret)) {
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Reclaim memory to be able to reuse it. */
ffa_ret = ffa_mem_reclaim(handle, 0);
if (is_ffa_call_error(ffa_ret)) {
ERROR("Failed to reclaim memory to be used in next test\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
ret = TEST_RESULT_SUCCESS;
exit:
unregister_custom_sync_exception_handler();
return ret;
}
/**
* Do a memory sharing operation over two fragments.
* Before the 2nd fragment the RX buffer is set in the realm PAS.
* The SPMC should fault, recover from it and return
* ffa_error(FFA_ERROR_ABORTED).
*
* Test Sequence:
* - Share memory with SP(1), using a force fragmented approach.
* - Initiate an hypervisor retrieve request, and retrieve only
* the first fragment.
* - Change the physical address space of NWd RX buffer.
* - Invoke the FFA_MEM_FRAG_RX interface, which should abort because
* of previous step.
* - Reestablish the PAS of the NWd RX buffer.
* - Contiueing with hypervisor retrieve request, and obtain the 2nd
* fragment.
* - Reclaim memory for clean-up of SPMC state.
*/
test_result_t test_ffa_memory_share_fragmented_rx_realm(void)
{
struct mailbox_buffers mb;
uint32_t remaining_constituent_count = 0;
uint32_t total_size;
uint32_t fragment_size;
uint32_t fragment_offset;
struct ffa_memory_access receiver = ffa_memory_access_init_permissions_from_mem_func(
SP_ID(1), FFA_MEM_SHARE_SMC32);
struct ffa_memory_region_constituent constituents[] = {
{(void *)four_share_pages, 4, 0},
{(void *)share_page, 1, 0}
};
struct ffa_value ffa_ret;
u_register_t ret_rmm;
test_result_t ret;
uint64_t handle;
if (get_armv9_2_feat_rme_support() == 0U) {
return TEST_RESULT_SKIPPED;
}
CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
GET_TFTF_MAILBOX(mb);
register_custom_sync_exception_handler(data_abort_handler);
/* Only send one constituent to start with. */
remaining_constituent_count = ffa_memory_region_init(
(struct ffa_memory_region *)mb.send, MAILBOX_SIZE, SENDER,
&receiver, 1, constituents, ARRAY_SIZE(constituents), 0,
0, FFA_MEMORY_NOT_SPECIFIED_MEM,
FFA_MEMORY_CACHE_WRITE_BACK,
FFA_MEMORY_INNER_SHAREABLE,
&total_size, &fragment_size);
/* It should have copied them all. */
if (remaining_constituent_count > 0) {
ERROR("Transaction descriptor initialization failed!\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/*
* Take the size of a constituent from the fragment to force the
* operation to be fragmented.
*/
fragment_size -= sizeof(struct ffa_memory_region_constituent);
ffa_ret = ffa_mem_share(total_size, fragment_size);
if (!is_expected_ffa_return(ffa_ret, FFA_MEM_FRAG_RX)) {
ERROR("Expected %s after the memory share.\n",
ffa_func_name(FFA_MEM_FRAG_RX));
ret = TEST_RESULT_FAIL;
goto exit;
}
handle = ffa_frag_handle(ffa_ret);
if (handle == FFA_MEMORY_HANDLE_INVALID) {
ERROR("SPMC returned an invalid handle for the operation.\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Prepare the next fragment for the operation. */
remaining_constituent_count = ffa_memory_fragment_init(
mb.send, PAGE_SIZE, &constituents[1], 1, &fragment_size);
ffa_ret = ffa_mem_frag_tx(handle, fragment_size);
if (is_ffa_call_error(ffa_ret)) {
ret = TEST_RESULT_FAIL;
goto exit;
}
/*
* Request the hypervisor retrieve request.
* Response should be fragmented.
*/
ffa_hypervisor_retrieve_request_init(mb.send, handle);
ffa_ret = ffa_mem_retrieve_req(sizeof(struct ffa_memory_region),
sizeof(struct ffa_memory_region));
if (ffa_func_id(ffa_ret) != FFA_MEM_RETRIEVE_RESP) {
ERROR("%s: couldn't retrieve the memory page. Error: %d\n",
__func__, ffa_error_code(ffa_ret));
ret = TEST_RESULT_FAIL;
goto exit;
}
total_size = ffa_mem_retrieve_res_total_size(ffa_ret);
fragment_size = ffa_mem_retrieve_res_frag_size(ffa_ret);
fragment_offset = fragment_size;
ret_rmm = host_rmi_granule_delegate((u_register_t)mb.recv);
if (ret_rmm != 0UL) {
INFO("Delegate operation returns 0x%lx for address %p\n",
ret_rmm, mb.send);
ret = TEST_RESULT_FAIL;
goto exit;
}
ffa_ret = ffa_rx_release();
if (is_ffa_call_error(ffa_ret)) {
ERROR("ffa_rx_release() failed.\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Call FFA_MEM_FRAG_RX but expect it to abort. */
ffa_ret = ffa_mem_frag_rx(handle, fragment_offset);
if (!is_expected_ffa_error(ffa_ret, FFA_ERROR_ABORTED)) {
ERROR("Expected FFA_MEM_FRAG_RX to have failed with"
"FFA_ERROR_ABORTED.\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Undelegate to reestablish the same security state for PAS. */
ret_rmm = host_rmi_granule_undelegate((u_register_t)mb.recv);
if (ret_rmm != 0UL) {
ERROR("Undelegate operation returns 0x%lx for address %llx\n",
ret_rmm, (uint64_t)mb.send);
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Continue the hypervisor retrieve request. */
if (!hypervisor_retrieve_request_continue(
&mb, handle, NULL, 0, total_size, fragment_offset, false)) {
ERROR("Failed to continue hypervisor retrieve request after"
" restablishing PAS.\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
/* Reclaim memory to be able to reuse it. */
ffa_ret = ffa_mem_reclaim(handle, 0);
if (is_ffa_call_error(ffa_ret)) {
ERROR("Failed to reclaim memory to be used in next test\n");
ret = TEST_RESULT_FAIL;
goto exit;
}
ret = TEST_RESULT_SUCCESS;
exit:
unregister_custom_sync_exception_handler();
return ret;
}