blob: b094a7a21595a546894b48d268bf3869f209ee37 [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 "hftest.h"
#include "primary_with_secondary.h"
#include "util.h"
alignas(PAGE_SIZE) static uint8_t page[PAGE_SIZE];
/**
* Tries sharing memory in different modes with different VMs and asserts that
* it will fail.
*/
void check_cannot_share_memory(void *ptr, size_t size)
{
uint32_t vms[] = {SERVICE_VM0, SERVICE_VM1};
enum hf_share modes[] = {HF_MEMORY_GIVE, HF_MEMORY_LEND,
HF_MEMORY_SHARE};
int i;
int j;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
for (j = 0; j < ARRAY_SIZE(modes); ++j) {
ASSERT_EQ(hf_share_memory(vms[i], (hf_ipaddr_t)ptr,
size, modes[j]),
-1);
}
}
}
/**
* Helper function to test lend memory in the different configurations.
*/
static void spci_check_cannot_lend_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[])
{
enum spci_lend_access lend_access[] = {SPCI_LEND_RO_NX, SPCI_LEND_RO_X,
SPCI_LEND_RW_NX, SPCI_LEND_RW_X};
enum spci_lend_type lend_type[] = {
SPCI_LEND_NORMAL_MEM, SPCI_LEND_DEV_NGNRNE, SPCI_LEND_DEV_NGNRE,
SPCI_LEND_DEV_NGRE, SPCI_LEND_DEV_GRE};
enum spci_lend_cacheability lend_cacheability[] = {
SPCI_LEND_CACHE_NON_CACHEABLE, SPCI_LEND_CACHE_WRITE_THROUGH,
SPCI_LEND_CACHE_WRITE_BACK};
enum spci_lend_shareability lend_shareability[] = {
SPCI_LEND_SHARE_NON_SHAREABLE, SPCI_LEND_RESERVED,
SPCI_LEND_OUTER_SHAREABLE, SPCI_LEND_INNER_SHAREABLE};
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM0, SERVICE_VM1};
int i = 0;
int j = 0;
int k = 0;
int l = 0;
int m = 0;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
for (j = 0; j < ARRAY_SIZE(lend_access); ++j) {
for (k = 0; k < ARRAY_SIZE(lend_type); ++k) {
for (l = 0; l < ARRAY_SIZE(lend_cacheability);
++l) {
for (m = 0;
m < ARRAY_SIZE(lend_shareability);
++m) {
spci_memory_lend(
mb.send, vms[i],
HF_PRIMARY_VM_ID,
constituents, 1, 0,
lend_access[j],
lend_type[k],
lend_cacheability[l],
lend_shareability[m]);
EXPECT_EQ(
spci_msg_send(0),
SPCI_INVALID_PARAMETERS);
}
}
}
}
}
}
/**
* 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 spci_check_cannot_donate_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[], int num_elements,
int32_t avoid_vm)
{
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM0, SERVICE_VM1};
int i;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
/* Optionally skip one VM as the donate would succeed. */
if (vms[i] == avoid_vm) {
continue;
}
spci_memory_donate(mb.send, vms[i], HF_PRIMARY_VM_ID,
constituents, num_elements, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
}
/**
* Tries relinquishing memory with different VMs and asserts that
* it will fail.
*/
static void spci_check_cannot_relinquish_memory(
struct mailbox_buffers mb,
struct spci_memory_region_constituent constituents[], int num_elements)
{
uint32_t vms[] = {HF_PRIMARY_VM_ID, SERVICE_VM0, SERVICE_VM1};
int i;
int j;
for (i = 0; i < ARRAY_SIZE(vms); ++i) {
for (j = 0; j < ARRAY_SIZE(vms); ++j) {
spci_memory_relinquish(mb.send, vms[i], vms[j],
constituents, num_elements, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
}
}
/**
* After memory has been shared concurrently, it can't be shared again.
*/
TEST(memory_sharing, cannot_share_concurrent_memory_twice)
{
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_SHARE),
0);
check_cannot_share_memory(page, PAGE_SIZE);
}
/**
* After memory has been given away, it can't be shared again.
*/
TEST(memory_sharing, cannot_share_given_memory_twice)
{
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_GIVE),
0);
check_cannot_share_memory(page, PAGE_SIZE);
}
/**
* After memory has been lent, it can't be shared again.
*/
TEST(memory_sharing, cannot_share_lent_memory_twice)
{
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
check_cannot_share_memory(page, PAGE_SIZE);
}
/**
* Sharing memory concurrently gives both VMs access to the memory so it can be
* used for communication.
*/
TEST(memory_sharing, concurrent)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_increment", mb.send);
memset_s(ptr, sizeof(page), 'a', PAGE_SIZE);
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_SHARE),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
for (int i = 0; i < PAGE_SIZE; ++i) {
page[i] = i;
}
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
for (int i = 0; i < PAGE_SIZE; ++i) {
uint8_t value = i + 1;
EXPECT_EQ(page[i], value);
}
}
/**
* Memory shared concurrently can be returned to the owner.
*/
TEST(memory_sharing, share_concurrently_and_get_back)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_return", mb.send);
/* Dirty the memory before sharing it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_SHARE),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be returned. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI Memory given away can be given back.
* Employing SPCI donate architected messages.
*/
TEST(memory_sharing, spci_give_and_get_back)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_return", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
/* Can only donate single constituent memory region. */
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
run_res = hf_vcpu_run(SERVICE_VM0, 0);
/* Let the memory be returned. */
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Ensure that the secondary VM accessed the region. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'c');
}
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Check that memory can be lent and is accessible by both parties.
*/
TEST(memory_sharing, spci_lend_relinquish)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
run_res = hf_vcpu_run(SERVICE_VM0, 0);
/* Let the memory be returned. */
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Ensure that the secondary VM accessed the region. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'c');
}
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* Memory given away can be given back.
*/
TEST(memory_sharing, give_and_get_back)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_return", mb.send);
/* Dirty the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_GIVE),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be returned. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* Memory that has been lent can be returned to the owner.
*/
TEST(memory_sharing, lend_and_get_back)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_return", mb.send);
/* Dirty the memory before lending it. */
memset_s(ptr, sizeof(page), 'c', PAGE_SIZE);
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be returned. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* After memory has been returned, it is free to be shared again.
*/
TEST(memory_sharing, reshare_after_return)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_return", mb.send);
/* Share the memory initially. */
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be returned. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Share the memory again after it has been returned. */
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
/* Observe the service doesn't fault when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
}
/**
* After memory has been returned, it is free to be shared with another VM.
*/
TEST(memory_sharing, share_elsewhere_after_return)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "memory_return", mb.send);
/* Share the memory initially. */
ASSERT_EQ(hf_share_memory(SERVICE_VM0, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
/*
* TODO: the address of the memory will be part of the proper API. That
* API is still to be agreed on so the address is passed
* explicitly to test the mechanism.
*/
memcpy_s(mb.send->payload, SPCI_MSG_PAYLOAD_MAX, &ptr, sizeof(ptr));
spci_message_init(mb.send, sizeof(ptr), SERVICE_VM0, HF_PRIMARY_VM_ID);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be returned. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Share the memory with a differnt VM after it has been returned. */
ASSERT_EQ(hf_share_memory(SERVICE_VM1, (hf_ipaddr_t)&page, PAGE_SIZE,
HF_MEMORY_LEND),
0);
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* After memory has been given, it is no longer accessible by the sharing VM.
*/
TEST(memory_sharing, give_memory_and_lose_access)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr;
SERVICE_SELECT(SERVICE_VM0, "give_memory_and_fault", mb.send);
/* Have the memory be given. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Check the memory was cleared. */
ptr = *(uint8_t **)mb.recv->payload;
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
/* Observe the service fault when it tries to access it. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* After memory has been lent, it is no longer accessible by the sharing VM.
*/
TEST(memory_sharing, lend_memory_and_lose_access)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr;
SERVICE_SELECT(SERVICE_VM0, "lend_memory_and_fault", mb.send);
/* Have the memory be lent. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Check the memory was cleared. */
ptr = *(uint8_t **)mb.recv->payload;
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 0);
}
/* Observe the service fault when it tries to access it. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Verify past the upper bound of the donated region cannot be accessed.
*/
TEST(memory_sharing, spci_donate_check_upper_bounds)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_donate_check_upper_bound", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Verify past the lower bound of the donated region cannot be accessed.
*/
TEST(memory_sharing, spci_donate_check_lower_bounds)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_donate_check_lower_bound", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Observe the service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: After memory has been returned, it is free to be shared with another
* VM.
*/
TEST(memory_sharing, spci_donate_elsewhere_after_return)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_return", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_return", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
run_res = hf_vcpu_run(SERVICE_VM0, 0);
/* Let the memory be returned. */
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Share the memory with another VM. */
spci_memory_donate(mb.send, SERVICE_VM1, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Observe the original service faulting when accessing the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: 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, spci_donate_vms)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_donate_secondary_and_fault", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
/* Set up VM1 to wait for message. */
run_res = hf_vcpu_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
/* Donate memory. */
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be sent from VM0 to VM1. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Receive memory in VM1. */
run_res = hf_vcpu_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* Try to access memory in VM0 and fail. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
/* Ensure that memory in VM1 remains the same. */
run_res = hf_vcpu_run(SERVICE_VM1, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
}
/**
* SPCI: Check that memory is unable to be donated to multiple parties.
*/
TEST(memory_sharing, spci_donate_twice)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_donate_twice", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', 1 * PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
/* Donate memory to VM0. */
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be received. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* Fail to share memory again with any VM. */
spci_check_cannot_donate_memory(mb, constituents, 1, -1);
/* Fail to relinquish memory from any VM. */
spci_check_cannot_relinquish_memory(mb, constituents, 1);
/* Let the memory be sent from VM0 to PRIMARY (returned). */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Check we have access again. */
ptr[0] = 'f';
/* Try and fail to donate memory from VM0 to VM1. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
}
/**
* SPCI: Check cannot donate to self.
*/
TEST(memory_sharing, spci_donate_to_self)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_donate(mb.send, HF_PRIMARY_VM_ID, HF_PRIMARY_VM_ID,
constituents, 1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
/**
* SPCI: Check cannot lend to self.
*/
TEST(memory_sharing, spci_lend_to_self)
{
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, HF_PRIMARY_VM_ID, HF_PRIMARY_VM_ID,
constituents, 1, 0, SPCI_LEND_RW_X,
SPCI_LEND_NORMAL_MEM, SPCI_LEND_CACHE_WRITE_BACK,
SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
/**
* SPCI: Check cannot donate from alternative VM.
*/
TEST(memory_sharing, spci_donate_invalid_source)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_donate_invalid_source", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_receive", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
/* Try invalid configurations. */
spci_memory_donate(mb.send, HF_PRIMARY_VM_ID, SERVICE_VM0, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
spci_memory_donate(mb.send, SERVICE_VM0, SERVICE_VM0, constituents, 1,
0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
spci_memory_donate(mb.send, SERVICE_VM0, SERVICE_VM1, constituents, 1,
0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
/* Successfully donate to VM0. */
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Receive and return memory from VM0. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Use VM0 to fail to donate memory from the primary to VM1. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
}
/**
* SPCI: Check that unaligned addresses can not be shared.
*/
TEST(memory_sharing, spci_give_and_get_back_unaligned)
{
struct mailbox_buffers mb = set_up_mailbox();
SERVICE_SELECT(SERVICE_VM0, "spci_memory_return", mb.send);
for (int i = 1; i < PAGE_SIZE; i++) {
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page + i, .page_count = 1},
};
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID,
constituents, 1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
spci_memory_lend(
mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents, 1,
0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
}
/**
* SPCI: Check cannot lend from alternative VM.
*/
TEST(memory_sharing, spci_lend_invalid_source)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_lend_invalid_source", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
/* Check cannot swap VM IDs. */
spci_memory_lend(mb.send, HF_PRIMARY_VM_ID, SERVICE_VM0, constituents,
1, 0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
/* Lend memory to VM0. */
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Receive and return memory from VM0. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Try to lend memory from primary in VM0. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
}
/**
* SPCI: Memory can be lent with executable permissions.
* Check RO and RW permissions.
*/
TEST(memory_sharing, spci_lend_relinquish_X_RW)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* 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 = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Re-initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
ptr[i]++;
}
/* Observe the service faulting when writing to the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Memory can be lent without executable permissions.
* Check RO and RW permissions.
*/
TEST(memory_sharing, spci_lend_relinquish_NX_RW)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_NX, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* 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 = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
/* Re-initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 'b', PAGE_SIZE);
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_NX, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* Ensure we still have access. */
for (int i = 0; i < PAGE_SIZE; ++i) {
ASSERT_EQ(ptr[i], 'b');
ptr[i]++;
}
/* Observe the service faulting when writing to the memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Exercise execution permissions for lending memory.
*/
TEST(memory_sharing, spci_lend_relinquish_RW_X)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish_X", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 0, PAGE_SIZE);
uint64_t *ptr2 = (uint64_t *)page;
/* Set memory to contain the RET instruction to attempt to execute. */
*ptr2 = 0xD65F03C0;
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Attempt to execute from memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RW_NX, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Try and fail to execute from the memory region. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Exercise execution permissions for lending memory without write access.
*/
TEST(memory_sharing, spci_lend_relinquish_RO_X)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish_X", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page), 0, PAGE_SIZE);
uint64_t *ptr2 = (uint64_t *)page;
/* Set memory to contain the RET instruction to attempt to execute. */
*ptr2 = 0xD65F03C0;
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 1},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Attempt to execute from memory. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_NX, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Try and fail to execute from the memory region. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_ABORTED);
}
/**
* SPCI: Memory can be lent, but then no part can be donated.
*/
TEST(memory_sharing, spci_lend_donate)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_relinquish_RW", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_relinquish_RW", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 2},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* 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++) {
constituents[0].address = (uint64_t)page + PAGE_SIZE;
spci_memory_donate(mb.send, SERVICE_VM1, HF_PRIMARY_VM_ID,
constituents, 1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
/* Ensure we can donate to the only borrower. */
spci_memory_donate(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
}
/**
* SPCI: Memory can be lent, but then no part can be lent again.
*/
TEST(memory_sharing, spci_lend_twice)
{
struct hf_vcpu_run_return run_res;
struct mailbox_buffers mb = set_up_mailbox();
uint8_t *ptr = page;
SERVICE_SELECT(SERVICE_VM0, "spci_memory_lend_twice", mb.send);
SERVICE_SELECT(SERVICE_VM1, "spci_memory_lend_twice", mb.send);
/* Initialise the memory before giving it. */
memset_s(ptr, sizeof(page) * 2, 'b', PAGE_SIZE * 2);
struct spci_memory_region_constituent constituents[] = {
{.address = (uint64_t)page, .page_count = 2},
};
spci_memory_lend(mb.send, SERVICE_VM0, HF_PRIMARY_VM_ID, constituents,
1, 0, SPCI_LEND_RO_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_SUCCESS);
/* Let the memory be accessed. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_YIELD);
/* Attempt to lend the same area of memory. */
spci_check_cannot_lend_memory(mb, constituents);
/* Fail to donate to VM apart from VM0. */
spci_check_cannot_donate_memory(mb, constituents, 1, SERVICE_VM0);
/* Fail to relinquish from any VM. */
spci_check_cannot_relinquish_memory(mb, constituents, 1);
/* Attempt to lend again with different permissions. */
constituents[0].page_count = 1;
for (int i = 1; i < PAGE_SIZE * 2; i++) {
constituents[0].address = (uint64_t)page + PAGE_SIZE;
spci_memory_lend(
mb.send, SERVICE_VM1, HF_PRIMARY_VM_ID, constituents, 1,
0, SPCI_LEND_RO_X, SPCI_LEND_NORMAL_MEM,
SPCI_LEND_CACHE_WRITE_BACK, SPCI_LEND_OUTER_SHAREABLE);
EXPECT_EQ(spci_msg_send(0), SPCI_INVALID_PARAMETERS);
}
}