blob: 5050f1199971261521bd9c2123585aee627ead2b [file] [log] [blame]
/*
* Copyright 2018 The Hafnium Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "hf/mm.h"
#include "hf/std.h"
#include "vmapi/hf/call.h"
#include "primary_with_secondary.h"
#include "test/hftest.h"
#include "test/vmapi/exception_handler.h"
#include "test/vmapi/spci.h"
alignas(PAGE_SIZE) static uint8_t pages[4 * PAGE_SIZE];
/**
* Helper function to test sending memory in the different configurations.
*/
static void check_cannot_send_memory(
struct mailbox_buffers mb,
struct spci_value (*send_function)(uint32_t, uint32_t, uint32_t),
struct spci_memory_region_constituent constituents[],
int constituent_count, int32_t avoid_vm)
{
enum spci_memory_access access[] = {SPCI_MEMORY_RO_NX, SPCI_MEMORY_RO_X,
SPCI_MEMORY_RW_NX,
SPCI_MEMORY_RW_X};
enum spci_memory_cacheability cacheability[] = {
SPCI_MEMORY_CACHE_NON_CACHEABLE,
SPCI_MEMORY_CACHE_WRITE_THROUGH, SPCI_MEMORY_CACHE_WRITE_BACK};
enum spci_memory_cacheability device[] = {
SPCI_MEMORY_DEV_NGNRNE, SPCI_MEMORY_DEV_NGNRE,
SPCI_MEMORY_DEV_NGRE, SPCI_MEMORY_DEV_GRE};
enum spci_memory_shareability shareability[] = {
SPCI_MEMORY_SHARE_NON_SHAREABLE, SPCI_MEMORY_RESERVED,
SPCI_MEMORY_OUTER_SHAREABLE, SPCI_MEMORY_INNER_SHAREABLE};
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM1, SERVICE_VM2};
size_t i = 0;
size_t j = 0;
size_t k = 0;
size_t l = 0;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
/* Optionally skip one VM as the send would succeed. */
if (vms[i] == avoid_vm) {
continue;
}
for (j = 0; j < ARRAY_SIZE(access); ++j) {
for (k = 0; k < ARRAY_SIZE(shareability); ++k) {
for (l = 0; l < ARRAY_SIZE(cacheability); ++l) {
uint32_t msg_size =
spci_memory_region_init(
mb.send,
HF_PRIMARY_VM_ID,
vms[i], constituents,
constituent_count, 0, 0,
access[j],
SPCI_MEMORY_NORMAL_MEM,
cacheability[l],
shareability[k]);
EXPECT_SPCI_ERROR(
send_function(msg_size,
msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
for (l = 0; l < ARRAY_SIZE(device); ++l) {
uint32_t msg_size =
spci_memory_region_init(
mb.send,
HF_PRIMARY_VM_ID,
vms[i], constituents,
constituent_count, 0, 0,
access[j],
SPCI_MEMORY_DEVICE_MEM,
device[l],
shareability[k]);
EXPECT_SPCI_ERROR(
send_function(msg_size,
msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
}
}
}
}
/**
* Helper function to test lending memory in the different configurations.
*/
static void check_cannot_lend_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[],
int constituent_count, int32_t avoid_vm)
{
check_cannot_send_memory(mb, spci_mem_lend, constituents,
constituent_count, avoid_vm);
}
/**
* Helper function to test sharing memory in the different configurations.
*/
static void check_cannot_share_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[],
int constituent_count, int32_t avoid_vm)
{
check_cannot_send_memory(mb, spci_mem_share, constituents,
constituent_count, avoid_vm);
}
/**
* Tries donating memory in available modes with different VMs and asserts that
* it will fail to all except the supplied VM ID as this would succeed if it
* is the only borrower.
*/
static void check_cannot_donate_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[],
int constituent_count, int32_t avoid_vm)
{
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM1, SERVICE_VM2};
size_t i;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
uint32_t msg_size;
/* Optionally skip one VM as the donate would succeed. */
if (vms[i] == avoid_vm) {
continue;
}
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, vms[i], constituents,
constituent_count, 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
}
/**
* Tries relinquishing memory with different VMs and asserts that
* it will fail.
*/
static void check_cannot_relinquish_memory(struct mailbox_buffers mb,
spci_memory_handle_t handle)
{
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM1, SERVICE_VM2};
size_t i;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
*(struct spci_mem_relinquish *)mb.send =
(struct spci_mem_relinquish){.handle = handle,
.sender = vms[i]};
EXPECT_SPCI_ERROR(spci_mem_relinquish(),
SPCI_INVALID_PARAMETERS);
}
}
TEAR_DOWN(memory_sharing)
{
EXPECT_SPCI_ERROR(spci_rx_release(), SPCI_DENIED);
}
/**
* Sharing memory concurrently gives both VMs access to the memory so it can be
* used for communication.
*/
TEST(memory_sharing, concurrent)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "memory_increment", mb.send);
memset_s(ptr, sizeof(pages), 'a', PAGE_SIZE);
send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
for (int i = 0; i < PAGE_SIZE; ++i) {
pages[i] = i;
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
for (int i = 0; i < PAGE_SIZE; ++i) {
uint8_t value = i + 1;
EXPECT_EQ(pages[i], value);
}
}
/**
* Memory shared concurrently can be returned to the owner.
*/
TEST(memory_sharing, share_concurrently_and_get_back)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish", mb.send);
/* Dirty the memory before sharing it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
handle = send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be returned. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'c');
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Device address space cannot be shared, only normal memory.
*/
TEST(memory_sharing, cannot_share_device_memory)
{
struct mailbox_buffers mb = set_up_mailbox();
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)PAGE_SIZE, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_return", mb.send);
check_cannot_lend_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
check_cannot_share_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
check_cannot_donate_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
}
/**
* Check that memory can be lent and is accessible by both parties.
*/
TEST(memory_sharing, lend_relinquish)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
spci_memory_handle_t handle;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init((uint64_t)pages + PAGE_SIZE,
2),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
/* Let the memory be returned. */
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Ensure that the secondary VM accessed the region. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'c');
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Check that memory that is donated can't be relinquished.
*/
TEST(memory_sharing, donate_relinquish)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_donate_relinquish", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init((uint64_t)pages + PAGE_SIZE,
2),
};
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/*
* Let the service access the memory, and try and fail to relinquish it.
*/
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
}
/**
* Memory given away can be given back.
*/
TEST(memory_sharing, give_and_get_back)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
/* Dirty the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be returned, and retrieve it. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'c');
}
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Memory that has been lent can be returned to the owner.
*/
TEST(memory_sharing, lend_and_get_back)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish", mb.send);
/* Dirty the memory before lending it. */
memset_s(ptr, sizeof(pages), 'c', PAGE_SIZE);
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be returned. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'd');
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* After memory has been returned, it is free to be lent again.
*/
TEST(memory_sharing, relend_after_return)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish", mb.send);
/* Lend the memory initially. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be returned. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Lend the memory again after it has been returned. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Observe the service doesn't fault when accessing the memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
}
/**
* After memory has been returned, it is free to be lent to another VM.
*/
TEST(memory_sharing, lend_elsewhere_after_return)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_lend_relinquish", mb.send);
/* Lend the memory initially. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be returned. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Share the memory with a different VM after it has been returned. */
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* After memory has been given, it is no longer accessible by the sharing VM.
*/
TEST(memory_sharing, give_memory_and_lose_access)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
struct spci_retrieved_memory_region *memory_region;
struct spci_receiver_address_range *range;
uint8_t *ptr;
SERVICE_SELECT(SERVICE_VM1, "give_memory_and_fault", mb.send);
/* Have the memory be given. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
/* Check the memory was cleared. */
memory_region = (struct spci_retrieved_memory_region *)mb.recv;
range = spci_retrieved_memory_region_first_receiver_range(
memory_region);
ptr = (uint8_t *)spci_memory_region_constituent_get_address(
&range->constituents[0]);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* After memory has been lent, it is no longer accessible by the sharing VM.
*/
TEST(memory_sharing, lend_memory_and_lose_access)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
struct spci_retrieved_memory_region *memory_region;
struct spci_receiver_address_range *range;
uint8_t *ptr;
SERVICE_SELECT(SERVICE_VM1, "lend_memory_and_fault", mb.send);
/* Have the memory be lent. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
/* Check the memory was cleared. */
memory_region = (struct spci_retrieved_memory_region *)mb.recv;
range = spci_retrieved_memory_region_first_receiver_range(
memory_region);
ptr = (uint8_t *)spci_memory_region_constituent_get_address(
&range->constituents[0]);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Verify past the upper bound of the donated region cannot be accessed.
*/
TEST(memory_sharing, donate_check_upper_bounds)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint64_t address;
SERVICE_SELECT(SERVICE_VM1, "spci_check_upper_bound", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_check_upper_bound", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
/* Specify non-contiguous memory regions. */
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 2, 1),
};
/*
* Specify that we want to test the first constituent of the donated
* memory region. This is utilised by the test service.
*/
pages[0] = 0;
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
/* Use different memory regions for verifying the second constituent. */
address = (uint64_t)pages + PAGE_SIZE * 1;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
address = (uint64_t)pages + PAGE_SIZE * 3;
constituents[1].address_high = address << 32;
constituents[1].address_low = (uint32_t)address;
/*
* Specify that we now want to test the second constituent of the
* donated memory region.
*/
pages[PAGE_SIZE] = 1;
/*
* Use the second secondary VM for this test as the first is now in an
* exception loop.
*/
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Verify past the lower bound of the donated region cannot be accessed.
*/
TEST(memory_sharing, donate_check_lower_bounds)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint64_t address;
SERVICE_SELECT(SERVICE_VM1, "spci_check_lower_bound", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_check_lower_bound", mb.send);
/* Initialise the memory before donating it. */
memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
/* Specify non-contiguous memory regions. */
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 2, 1),
};
/*
* Specify that we want to test the first constituent of the donated
* memory region. This is utilised by the test service.
*/
pages[0] = 0;
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
/* Use different memory regions for verifying the second constituent. */
address = (uint64_t)pages + PAGE_SIZE * 1;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
address = (uint64_t)pages + PAGE_SIZE * 3;
constituents[1].address_high = address << 32;
constituents[1].address_low = (uint32_t)address;
/*
* Specify that we now want to test the second constituent of the
* donated memory region.
*/
pages[PAGE_SIZE] = 1;
/*
* Use the second secondary VM for this test as the first is now in an
* exception loop.
*/
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* After memory has been returned, it is free to be shared with another
* VM.
*/
TEST(memory_sharing, donate_elsewhere_after_return)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_return", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
/* Let the memory be returned. */
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
/* Share the memory with another VM. */
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Check if memory can be donated between secondary VMs.
* Ensure that the memory can no longer be accessed by the first VM.
*/
TEST(memory_sharing, donate_vms)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_donate_secondary_and_fault", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
/* Set up VM2 to wait for message. */
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_WAIT_32);
/* Donate memory. */
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be sent from VM1 to VM2. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_msg_send_receiver(run_res), SERVICE_VM2);
/* Receive memory in VM2. */
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Try to access memory in VM1. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
/* Ensure that memory in VM2 remains the same. */
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
}
/**
* Check that memory is unable to be donated to multiple parties.
*/
TEST(memory_sharing, donate_twice)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_donate_twice", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
/* Donate memory to VM1. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be received. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Fail to share memory again with any VM. */
check_cannot_share_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
check_cannot_lend_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
check_cannot_donate_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
/* Fail to relinquish memory from any VM. */
check_cannot_relinquish_memory(mb, handle);
/* Let the memory be sent from VM1 to PRIMARY (returned). */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
/* Check we have access again. */
ptr[0] = 'f';
/* Try and fail to donate memory from VM1 to VM2. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
}
/**
* Check cannot donate to self.
*/
TEST(memory_sharing, donate_to_self)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, HF_PRIMARY_VM_ID, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/**
* Check cannot lend to self.
*/
TEST(memory_sharing, lend_to_self)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, HF_PRIMARY_VM_ID, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_lend(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/**
* Check cannot share to self.
*/
TEST(memory_sharing, share_to_self)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, HF_PRIMARY_VM_ID, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_share(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/**
* Check cannot donate from alternative VM.
*/
TEST(memory_sharing, donate_invalid_source)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_donate_invalid_source", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
/* Try invalid configurations. */
msg_size = spci_memory_region_init(
mb.send, SERVICE_VM1, HF_PRIMARY_VM_ID, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
msg_size = spci_memory_region_init(
mb.send, SERVICE_VM1, SERVICE_VM1, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
msg_size = spci_memory_region_init(
mb.send, SERVICE_VM2, SERVICE_VM1, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
/* Successfully donate to VM1. */
send_memory_and_retrieve_request(
SPCI_MEM_DONATE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Receive and return memory from VM1. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(retrieve_memory_from_message(mb.recv, mb.send, run_res, NULL),
SERVICE_VM1);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
/* Use VM1 to fail to donate memory from the primary to VM2. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
}
/**
* Check that unaligned addresses can not be shared.
*/
TEST(memory_sharing, give_and_get_back_unaligned)
{
struct mailbox_buffers mb = set_up_mailbox();
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
/* Check for unaligned pages for either constituent. */
for (int i = 0; i < PAGE_SIZE; i++) {
for (int j = 0; i < PAGE_SIZE; i++) {
/* Skip the case they're both aligned. */
if (i == 0 && j == 0) {
continue;
}
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init(
(uint64_t)pages + i, 1),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE + j, 1),
};
uint32_t msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, 0,
SPCI_MEMORY_RW_X, SPCI_MEMORY_NORMAL_MEM,
SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(
spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, 0,
SPCI_MEMORY_RW_X, SPCI_MEMORY_NORMAL_MEM,
SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_lend(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
}
}
/**
* Check cannot lend from alternative VM.
*/
TEST(memory_sharing, lend_invalid_source)
{
struct spci_value run_res;
spci_memory_handle_t handle;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_lend_invalid_source", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
/* Check cannot swap VM IDs. */
msg_size = spci_memory_region_init(
mb.send, SERVICE_VM1, HF_PRIMARY_VM_ID, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_lend(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
/* Lend memory to VM1. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Receive and return memory from VM1. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Try to lend memory from primary in VM1. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
}
/**
* Memory can be lent with executable permissions.
* Check RO and RW permissions.
*/
TEST(memory_sharing, lend_relinquish_X_RW)
{
struct spci_value run_res;
spci_memory_handle_t handle;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Let service write to and return memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Re-initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Memory can be shared with executable permissions.
* Check RO and RW permissions.
*/
TEST(memory_sharing, share_relinquish_X_RW)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
ptr[i]++;
}
/* Let service write to and return memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Re-initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
ptr[i]++;
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Memory can be shared without executable permissions.
* Check RO and RW permissions.
*/
TEST(memory_sharing, share_relinquish_NX_RW)
{
struct spci_value run_res;
spci_memory_handle_t handle;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_NX);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
}
/* Let service write to and return memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Re-initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE);
send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_NX);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
ptr[i]++;
}
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Test that memory which is shared cannot be cleared when it is relinquished.
*/
TEST(memory_sharing, share_relinquish_clear)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
spci_memory_handle_t handle;
struct spci_value run_res;
size_t i;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_share_relinquish_clear",
mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_NX);
/* Let the memory be received, fail to be cleared, and then returned. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
/* Check that it has not been cleared. */
for (i = 0; i < PAGE_SIZE * 2; ++i) {
ASSERT_EQ(ptr[i], 'b');
};
}
/**
* Exercise execution permissions for lending memory.
*/
TEST(memory_sharing, lend_relinquish_RW_X)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_X", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 0, PAGE_SIZE);
uint64_t *ptr2 = (uint64_t *)pages;
/* Set memory to contain the RET instruction to attempt to execute. */
*ptr2 = 0xD65F03C0;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Attempt to execute from memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_NX);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Exercise execution permissions for lending memory without write access.
*/
TEST(memory_sharing, lend_relinquish_RO_X)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_X", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 0, PAGE_SIZE);
uint64_t *ptr2 = (uint64_t *)pages;
/* Set memory to contain the RET instruction to attempt to execute. */
*ptr2 = 0xD65F03C0;
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_X);
/* Attempt to execute from memory. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_MSG_SEND_32);
EXPECT_EQ(spci_rx_release().func, SPCI_SUCCESS_32);
EXPECT_EQ(spci_mem_reclaim(handle, 0).func, SPCI_SUCCESS_32);
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_NX);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* Memory can be lent, but then no part can be donated.
*/
TEST(memory_sharing, lend_donate)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
};
/* Lend memory to VM1. */
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Ensure we can't donate any sub section of memory to another VM. */
constituents[0].page_count = 1;
for (int i = 1; i < PAGE_SIZE * 2; i++) {
uint64_t address = (uint64_t)pages + PAGE_SIZE;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/* Ensure we can't donate to the only borrower. */
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/**
* Memory can be shared, but then no part can be donated.
*/
TEST(memory_sharing, share_donate)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE * 4);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 2, 2),
};
send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RO_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Attempt to share the same area of memory. */
check_cannot_share_memory(mb, constituents, ARRAY_SIZE(constituents),
SERVICE_VM1);
/* Ensure we can't donate any sub section of memory to another VM. */
constituents[0].page_count = 1;
for (int i = 1; i < PAGE_SIZE * 2; i++) {
uint64_t address = (uint64_t)pages + PAGE_SIZE;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/* Ensure we can't donate to the only borrower. */
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RW_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_donate(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
/**
* Memory can be lent, but then no part can be lent again.
*/
TEST(memory_sharing, lend_twice)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_twice", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_lend_twice", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages), 'b', PAGE_SIZE * 4);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 3, 1),
};
/* Lend memory to VM1. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/* Attempt to lend the same area of memory. */
check_cannot_lend_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
/* Attempt to share the same area of memory. */
check_cannot_share_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
/* Fail to donate to VM apart from VM1. */
check_cannot_donate_memory(mb, constituents, ARRAY_SIZE(constituents),
SERVICE_VM1);
/* Fail to relinquish from any VM. */
check_cannot_relinquish_memory(mb, handle);
/* Now attempt to share only a portion of the same area of memory. */
struct spci_memory_region_constituent constituents_subsection[] = {
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 3, 1),
};
check_cannot_lend_memory(mb, constituents_subsection,
ARRAY_SIZE(constituents_subsection), -1);
check_cannot_donate_memory(mb, constituents_subsection,
ARRAY_SIZE(constituents_subsection),
SERVICE_VM1);
/* Attempt to lend again with different permissions. */
constituents[0].page_count = 1;
for (int i = 0; i < 2; i++) {
uint64_t address = (uint64_t)pages + i * PAGE_SIZE;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RO_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_lend(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
}
/**
* Memory can be shared, but then no part can be shared again.
*/
TEST(memory_sharing, share_twice)
{
spci_memory_handle_t handle;
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_twice", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_memory_lend_twice", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
};
handle = send_memory_and_retrieve_request(
SPCI_MEM_SHARE_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
/* Let the memory be accessed. */
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.func, SPCI_YIELD_32);
/*
* Attempting to share or lend the same area of memory with any VM
* should fail.
*/
check_cannot_share_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
check_cannot_lend_memory(mb, constituents, ARRAY_SIZE(constituents),
-1);
/* Fail to donate to VM apart from VM1. */
check_cannot_donate_memory(mb, constituents, ARRAY_SIZE(constituents),
SERVICE_VM1);
/* Fail to relinquish from any VM. */
check_cannot_relinquish_memory(mb, handle);
/* Attempt to share again with different permissions. */
constituents[0].page_count = 1;
for (int i = 0; i < 2; i++) {
uint64_t address = (uint64_t)pages + i * PAGE_SIZE;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2, constituents,
ARRAY_SIZE(constituents), 0, 0, SPCI_MEMORY_RO_X,
SPCI_MEMORY_NORMAL_MEM, SPCI_MEMORY_CACHE_WRITE_BACK,
SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_share(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
}
}
/**
* Memory can be cleared while being lent.
*/
TEST(memory_sharing, lend_clear)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
spci_memory_handle_t handle;
size_t i;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
};
/* Lend memory with clear flag. */
handle = send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents),
SPCI_MEMORY_REGION_FLAG_CLEAR, SPCI_MEMORY_RO_X);
/* Take it back again. */
spci_mem_reclaim(handle, 0);
/* Check that it has not been cleared. */
for (i = 0; i < PAGE_SIZE * 2; ++i) {
ASSERT_EQ(ptr[i], 0);
};
}
/**
* Memory cannot be cleared while being shared.
*/
TEST(memory_sharing, share_clear)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint32_t msg_size;
size_t i;
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(pages) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 2),
};
msg_size = spci_memory_region_init(
mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1, constituents,
ARRAY_SIZE(constituents), 0, SPCI_MEMORY_REGION_FLAG_CLEAR,
SPCI_MEMORY_RO_X, SPCI_MEMORY_NORMAL_MEM,
SPCI_MEMORY_CACHE_WRITE_BACK, SPCI_MEMORY_OUTER_SHAREABLE);
EXPECT_SPCI_ERROR(spci_mem_share(msg_size, msg_size, 0),
SPCI_INVALID_PARAMETERS);
/* Check that it has not been cleared. */
for (i = 0; i < PAGE_SIZE * 2; ++i) {
ASSERT_EQ(ptr[i], 'b');
};
}
/**
* SPCI: Verify past the upper bound of the lent region cannot be accessed.
*/
TEST(memory_sharing, spci_lend_check_upper_bounds)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint64_t address;
SERVICE_SELECT(SERVICE_VM1, "spci_check_upper_bound", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_check_upper_bound", mb.send);
/* Initialise the memory before lending it. */
memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
/* Specify non-contiguous memory regions. */
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 2, 1),
};
/*
* Specify that we want to test the first constituent of the donated
* memory region. This is utilised by the test service.
*/
pages[0] = 0;
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
/* Use different memory regions for verifying the second constituent. */
address = (uint64_t)pages + PAGE_SIZE * 1;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
address = (uint64_t)pages + PAGE_SIZE * 3;
constituents[1].address_high = address << 32;
constituents[1].address_low = (uint32_t)address;
/*
* Specify that we now want to test the second constituent of the
* lent memory region.
*/
pages[PAGE_SIZE] = 1;
/*
* Use the second secondary VM for this test as the first is now in an
* exception loop.
*/
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}
/**
* SPCI: Verify past the lower bound of the lent region cannot be accessed.
*/
TEST(memory_sharing, spci_lend_check_lower_bounds)
{
struct spci_value run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = pages;
uint64_t address;
SERVICE_SELECT(SERVICE_VM1, "spci_check_lower_bound", mb.send);
SERVICE_SELECT(SERVICE_VM2, "spci_check_lower_bound", mb.send);
/* Initialise the memory before lending it. */
memset_s(ptr, sizeof(pages), 'b', 4 * PAGE_SIZE);
/* Specify non-contiguous memory regions. */
struct spci_memory_region_constituent constituents[] = {
spci_memory_region_constituent_init((uint64_t)pages, 1),
spci_memory_region_constituent_init(
(uint64_t)pages + PAGE_SIZE * 2, 1),
};
/*
* Specify that we want to test the first constituent of the lent
* memory region. This is utilised by the test service.
*/
pages[0] = 0;
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM1,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM1, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
/* Use different memory regions for verifying the second constituent. */
address = (uint64_t)pages + PAGE_SIZE * 1;
constituents[0].address_high = address << 32;
constituents[0].address_low = (uint32_t)address;
address = (uint64_t)pages + PAGE_SIZE * 3;
constituents[1].address_high = address << 32;
constituents[1].address_low = (uint32_t)address;
/*
* Specify that we now want to test the second constituent of the
* lent memory region.
*/
pages[PAGE_SIZE] = 1;
/*
* Use the second secondary VM for this test as the first is now in an
* exception loop.
*/
send_memory_and_retrieve_request(
SPCI_MEM_LEND_32, mb.send, HF_PRIMARY_VM_ID, SERVICE_VM2,
constituents, ARRAY_SIZE(constituents), 0, SPCI_MEMORY_RW_X);
run_res = spci_run(SERVICE_VM2, 0);
EXPECT_EQ(exception_handler_receive_exception_count(&run_res, mb.recv),
1);
}