blob: a2a96efdf0198db382bfa4b368f6a97ded02b7d9 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/maybe-standalone-test/maybe-standalone.h>
#include <lib/zx/bti.h>
#include <lib/zx/iommu.h>
#include <lib/zx/pager.h>
#include <lib/zx/vmo.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/iommu.h>
#include <zxtest/zxtest.h>
#include "helpers.h"
namespace {
void CreateVmoWithCharFill(zx::vmo* vmo, char content, size_t size) {
char buf[size];
memset(buf, content, size);
ASSERT_OK(zx::vmo::create(size, 0, vmo));
ASSERT_OK(vmo->write(buf, 0, size));
}
TEST(VmoTransferDataTestCase, DestroyedParentWithNonZeroOffset) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kChildSize = kPageSize * 3;
const uint64_t kChildOffset = kPageSize;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination of the transfer.
// In this test case, the source VMO is offset from the parent VMO by kChildOffset, and the
// parent is subsequently deleted. This will allow us to test the case where we have to
// `TakePages` from a source VMO with non-zero list_skew_.
zx::vmo parent_vmo;
CreateVmoWithCharFill(&parent_vmo, 's', kSize);
zx::vmo src_vmo;
ASSERT_OK(parent_vmo.create_child(ZX_VMO_CHILD_SNAPSHOT, kChildOffset, kChildSize, &src_vmo));
parent_vmo.reset();
zx::vmo dst_vmo;
CreateVmoWithCharFill(&dst_vmo, 'd', kSize);
// Verify that the transfer from source still works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kChildSize, &src_vmo, 0));
// Verify that the src VMO has been zeroed out.
ASSERT_OK(src_vmo.read(got, 0, kChildSize));
memset(expected, 0, kChildSize);
EXPECT_BYTES_EQ(got, expected, kChildSize);
// Verify that the dst VMO correctly received the transferred data.
ASSERT_OK(dst_vmo.read(got, 0, kSize));
memset(expected, 's', kChildSize);
memset(&expected[kChildSize], 'd', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
}
TEST(VmoTransferDataTestCase, SnapshotChildSrc) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kChildSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// TODO(https://fxbug.dev/42074633): Add ZX_VMO_CHILD_SNAPSHOT_MODIFIED to this list.
uint32_t child_types[] = {ZX_VMO_CHILD_SNAPSHOT, ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE};
for (auto child_type : child_types) {
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo parent_vmo;
if (child_type == ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE) {
// If the child type is SNAPSHOT_AT_LEAST_ON_WRITE, we need the parent VMO to be pager
// backed, as children of this type are upgraded to pure snapshots for anonymous VMOs.
zx::pager pager;
zx::port port;
ASSERT_OK(zx::pager::create(0, &pager));
ASSERT_OK(zx::port::create(0, &port));
ASSERT_OK(zx_pager_create_vmo(pager.get(), 0, port.get(), 0, kSize,
parent_vmo.reset_and_get_address()));
// Presupply pages to the pager backed VMO so that we don't need to wait for a request.
zx::vmo aux_vmo;
CreateVmoWithCharFill(&aux_vmo, 's', kSize);
ASSERT_OK(pager.supply_pages(parent_vmo, 0, kSize, aux_vmo, 0));
} else {
// If the child type is a pure snapshot, create an anonymous VMO.
CreateVmoWithCharFill(&parent_vmo, 's', kSize);
}
zx::vmo src_vmo;
ASSERT_OK(parent_vmo.create_child(child_type, 0, kChildSize, &src_vmo));
zx::vmo dst_vmo;
CreateVmoWithCharFill(&dst_vmo, 'd', kSize);
// Verify that transferring data from a snapshot child works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kChildSize, &src_vmo, 0));
// Verify that the src VMO has been zeroed out.
ASSERT_OK(src_vmo.read(got, 0, kChildSize));
memset(expected, 0, kChildSize);
EXPECT_BYTES_EQ(got, expected, kChildSize);
// Verify that the parent of the src VMO is unaffected.
ASSERT_OK(parent_vmo.read(got, 0, kSize));
memset(expected, 's', kSize);
EXPECT_BYTES_EQ(got, expected, kSize);
// Verify that the dst VMO correctly received the transferred data.
ASSERT_OK(dst_vmo.read(got, 0, kSize));
memset(expected, 's', kChildSize);
memset(&expected[kChildSize], 'd', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
}
}
TEST(VmoTransferDataTestCase, SnapshotChildDst) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kChildSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// TODO(https://fxbug.dev/42074633): Add ZX_VMO_CHILD_SNAPSHOT_MODIFIED to this list.
uint32_t child_types[] = {ZX_VMO_CHILD_SNAPSHOT, ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE};
for (auto child_type : child_types) {
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo src_vmo;
CreateVmoWithCharFill(&src_vmo, 's', kSize);
zx::vmo parent_vmo;
if (child_type == ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE) {
// If the child type is SNAPSHOT_AT_LEAST_ON_WRITE, we need the parent VMO to be pager
// backed, as children of this type are upgraded to pure snapshots for anonymous VMOs.
zx::pager pager;
zx::port port;
ASSERT_OK(zx::pager::create(0, &pager));
ASSERT_OK(zx::port::create(0, &port));
ASSERT_OK(zx_pager_create_vmo(pager.get(), 0, port.get(), 0, kSize,
parent_vmo.reset_and_get_address()));
// Presupply pages to the pager backed VMO so that we don't need to wait for a request.
zx::vmo aux_vmo;
CreateVmoWithCharFill(&aux_vmo, 'd', kSize);
ASSERT_OK(pager.supply_pages(parent_vmo, 0, kSize, aux_vmo, 0));
} else {
// If the child type is a pure snapshot, create an anonymous VMO.
CreateVmoWithCharFill(&parent_vmo, 'd', kSize);
}
zx::vmo dst_vmo;
ASSERT_OK(parent_vmo.create_child(child_type, 0, kChildSize, &dst_vmo));
// Verify that transferring data to a snapshot child works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kChildSize, &src_vmo, 0));
// Verify that the destination has the transferred contents.
ASSERT_OK(dst_vmo.read(got, 0, kChildSize));
memset(expected, 's', kChildSize);
EXPECT_BYTES_EQ(got, expected, kChildSize);
// Verify that the src vmo was zeroed out in the transfer range.
ASSERT_OK(src_vmo.read(got, 0, kSize));
memset(expected, 0, kChildSize);
memset(&expected[kChildSize], 's', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
// Verify that the parent of the destination remained unchanged.
memset(expected, 'd', kSize);
ASSERT_OK(parent_vmo.read(got, 0, kSize));
EXPECT_BYTES_EQ(got, expected, kSize);
}
}
TEST(VmoTransferDataTestCase, ChildSliceDst) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kChildSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo src_vmo;
CreateVmoWithCharFill(&src_vmo, 's', kSize);
zx::vmo parent_vmo;
CreateVmoWithCharFill(&parent_vmo, 'd', kSize);
zx::vmo dst_vmo;
ASSERT_OK(parent_vmo.create_child(ZX_VMO_CHILD_SLICE, 0, kChildSize, &dst_vmo));
// Verify that transferring data to a VMO that is a child slice works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kChildSize, &src_vmo, 0));
ASSERT_OK(src_vmo.read(got, 0, kSize));
memset(expected, 0, kChildSize);
memset(&expected[kChildSize], 's', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(parent_vmo.read(got, 0, kSize));
memset(expected, 's', kChildSize);
memset(&expected[kChildSize], 'd', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(dst_vmo.read(got, 0, kChildSize));
memset(expected, 's', kChildSize);
EXPECT_BYTES_EQ(got, expected, kChildSize);
}
TEST(VmoTransferDataTestCase, ChildSliceSrc) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kChildSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo parent_vmo;
CreateVmoWithCharFill(&parent_vmo, 's', kSize);
zx::vmo src_vmo;
ASSERT_OK(parent_vmo.create_child(ZX_VMO_CHILD_SLICE, 0, kChildSize, &src_vmo));
zx::vmo dst_vmo;
CreateVmoWithCharFill(&dst_vmo, 'd', kSize);
// Verify that transferring data from a VMO that is a child slice works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kChildSize, &src_vmo, 0));
ASSERT_OK(src_vmo.read(got, 0, kChildSize));
memset(expected, 0, kChildSize);
EXPECT_BYTES_EQ(got, expected, kChildSize);
ASSERT_OK(parent_vmo.read(got, 0, kSize));
memset(expected, 0, kChildSize);
memset(&expected[kChildSize], 's', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(dst_vmo.read(got, 0, kSize));
memset(expected, 's', kChildSize);
memset(&expected[kChildSize], 'd', kSize - kChildSize);
EXPECT_BYTES_EQ(got, expected, kSize);
}
TEST(VmoTransferDataTestCase, ReferenceChildSrc) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kTransferSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo parent_vmo;
CreateVmoWithCharFill(&parent_vmo, 's', kSize);
zx::vmo src_vmo;
ASSERT_OK(parent_vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &src_vmo));
zx::vmo dst_vmo;
CreateVmoWithCharFill(&dst_vmo, 'd', kSize);
// Verify that transferring data from a VMO that is a reference child works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kTransferSize, &src_vmo, 0));
ASSERT_OK(src_vmo.read(got, 0, kTransferSize));
memset(expected, 0, kTransferSize);
EXPECT_BYTES_EQ(got, expected, kTransferSize);
ASSERT_OK(parent_vmo.read(got, 0, kSize));
memset(expected, 0, kTransferSize);
memset(&expected[kTransferSize], 's', kSize - kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(dst_vmo.read(got, 0, kSize));
memset(expected, 's', kTransferSize);
memset(&expected[kTransferSize], 'd', kSize - kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
}
TEST(VmoTransferDataTestCase, ReferenceChildDst) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kTransferSize = kPageSize * 3;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo src_vmo;
CreateVmoWithCharFill(&src_vmo, 's', kSize);
zx::vmo parent_vmo;
CreateVmoWithCharFill(&parent_vmo, 'd', kSize);
zx::vmo dst_vmo;
ASSERT_OK(parent_vmo.create_child(ZX_VMO_CHILD_REFERENCE, 0, 0, &dst_vmo));
// Verify that transferring data to a VMO that is a reference child works.
ASSERT_OK(dst_vmo.transfer_data(0, 0, kTransferSize, &src_vmo, 0));
ASSERT_OK(src_vmo.read(got, 0, kSize));
memset(expected, 0, kTransferSize);
memset(&expected[kTransferSize], 's', kSize - kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(parent_vmo.read(got, 0, kSize));
memset(expected, 's', kTransferSize);
memset(&expected[kTransferSize], 'd', kSize - kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
ASSERT_OK(dst_vmo.read(got, 0, kTransferSize));
memset(expected, 's', kTransferSize);
EXPECT_BYTES_EQ(got, expected, kTransferSize);
}
TEST(VmoTransferDataTestCase, SameSrcAndDst) {
// Verify that passing the same VMO as the source and destination succeeds.
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kTransferSize = kPageSize * 3;
const uint64_t kTransferOffset = kPageSize * 2;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo vmo;
CreateVmoWithCharFill(&vmo, 's', kSize);
ASSERT_OK(vmo.transfer_data(0, kTransferOffset, kTransferSize, &vmo, 0));
ASSERT_OK(vmo.read(got, 0, kSize));
memset(expected, 0, kTransferOffset);
memset(&expected[kTransferOffset], 's', kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
}
TEST(VmoTransferDataTestCase, Basic) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kSize = kPageSize * 5;
const uint64_t kTransferSize = kPageSize * 3;
const uint64_t kTransferOffset = kPageSize * 2;
char got[kSize];
char expected[kSize];
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo src_vmo;
CreateVmoWithCharFill(&src_vmo, 's', kSize);
zx::vmo dst_vmo;
CreateVmoWithCharFill(&dst_vmo, 'd', kSize);
// Verify the happy case works by transfer the first three pages of the
// original VMO into the last three pages of the destination VMO.
ASSERT_OK(dst_vmo.transfer_data(0, kTransferOffset, kTransferSize, &src_vmo, 0));
// Validate that the destination VMO retains its original contents for the first two pages and
// contains the transferred data for the last three pages.
ASSERT_OK(dst_vmo.read(got, 0, kSize));
memset(expected, 'd', kTransferOffset);
memset(&expected[kTransferOffset], 's', kTransferSize);
EXPECT_BYTES_EQ(got, expected, kSize);
// Validate that the transferred pages were zeroed out in the source VMO.
ASSERT_OK(src_vmo.read(got, 0, kSize));
memset(expected, 0, kTransferSize);
memset(&expected[kTransferSize], 's', kTransferOffset);
EXPECT_BYTES_EQ(got, expected, kSize);
}
TEST(VmoTransferDataTestCase, InvalidInputs) {
const uint64_t kPageSize = zx_system_get_page_size();
const uint64_t kPageCount = 5;
const uint64_t kSize = kPageSize * kPageCount;
// Create VMOs to act as the source and destination VMOs for a transfer.
zx::vmo src_vmo;
ASSERT_OK(zx::vmo::create(kSize, 0, &src_vmo));
zx::vmo dst_vmo;
ASSERT_OK(zx::vmo::create(kSize, 0, &dst_vmo));
// Verify that a bad handle as the source or destination VMO fails.
zx::vmo invalid_vmo;
EXPECT_EQ(ZX_ERR_BAD_HANDLE, dst_vmo.transfer_data(0, kPageSize, kPageSize, &invalid_vmo, 0));
EXPECT_EQ(ZX_ERR_BAD_HANDLE, invalid_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
// Verify that passing non-zero options fails.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, dst_vmo.transfer_data(1, kPageSize - 1, kPageSize, &src_vmo, 0));
// Verify that non-page aligned offsets/lengths fail.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, dst_vmo.transfer_data(0, kPageSize - 1, kPageSize, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, dst_vmo.transfer_data(0, kPageSize, kPageSize - 1, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_INVALID_ARGS,
dst_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, kPageSize - 1));
// Verify that not having the appropriate rights on the source and destination fails.
// 1. Src with no rights fails.
// 2. Src with only read right fails.
// 3. Src with only write right fails.
// 4. Dst with no rights fails.
zx::vmo src_no_rights;
ASSERT_OK(
zx_handle_duplicate(src_vmo.get(), ZX_RIGHT_NONE, src_no_rights.reset_and_get_address()));
EXPECT_EQ(ZX_ERR_ACCESS_DENIED,
dst_vmo.transfer_data(0, kPageSize, kPageSize, &src_no_rights, 0));
zx::vmo src_no_write;
ASSERT_OK(
zx_handle_duplicate(src_vmo.get(), ZX_RIGHT_READ, src_no_write.reset_and_get_address()));
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, dst_vmo.transfer_data(0, kPageSize, kPageSize, &src_no_write, 0));
zx::vmo src_no_read;
ASSERT_OK(
zx_handle_duplicate(src_vmo.get(), ZX_RIGHT_WRITE, src_no_read.reset_and_get_address()));
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, dst_vmo.transfer_data(0, kPageSize, kPageSize, &src_no_read, 0));
zx::vmo dst_no_rights;
ASSERT_OK(
zx_handle_duplicate(dst_vmo.get(), ZX_RIGHT_NONE, dst_no_rights.reset_and_get_address()));
EXPECT_EQ(ZX_ERR_ACCESS_DENIED,
dst_no_rights.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
// Verify that a pager-backed source or destination VMO fails.
zx::pager pager;
ASSERT_OK(zx::pager::create(0, &pager));
zx::port port;
ASSERT_OK(zx::port::create(0, &port));
zx::vmo pager_backed_vmo;
ASSERT_OK(pager.create_vmo(0, port, 1, kSize, &pager_backed_vmo));
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED,
dst_vmo.transfer_data(0, kPageSize, kPageSize, &pager_backed_vmo, 0));
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED,
pager_backed_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
// Verify that providing an invalid range to the source VMO fails.
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, dst_vmo.transfer_data(0, kPageSize, 6 * kPageSize, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE,
dst_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 6 * kPageSize));
// Verify that providing an invalid range to the destination VMO fails.
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE,
dst_vmo.transfer_data(0, kPageSize * 6, kPageSize, &src_vmo, 3 * kPageSize));
// Verify that providing a physical, contiguous, or pinned VMO as either the destination or the
// source fails.
zx::unowned_resource system_resource = maybe_standalone::GetSystemResource();
if (!system_resource->is_valid()) {
printf("System resource not available, skipping\n");
return;
}
zx::result<zx::resource> result =
maybe_standalone::GetSystemResourceWithBase(system_resource, ZX_RSRC_SYSTEM_IOMMU_BASE);
ASSERT_OK(result.status_value());
zx::resource iommu_resource = std::move(result.value());
zx::result res = vmo_test::GetTestPhysVmo(kSize);
ASSERT_OK(res.status_value());
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED,
res.value().vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED,
dst_vmo.transfer_data(0, kPageSize, kPageSize, &res.value().vmo, 0));
zx::iommu iommu;
zx::bti bti;
zx_iommu_desc_dummy_t desc;
auto final_bti_check = vmo_test::CreateDeferredBtiCheck(bti);
ASSERT_OK(zx::iommu::create(iommu_resource, ZX_IOMMU_TYPE_DUMMY, &desc, sizeof(desc), &iommu));
bti = vmo_test::CreateNamedBti(iommu, 0, 0xdeadbeef, "VmoTestCase::TransferData");
zx::vmo contig_vmo;
ASSERT_OK(zx::vmo::create_contiguous(bti, kSize, 0, &contig_vmo));
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, contig_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, dst_vmo.transfer_data(0, kPageSize, kPageSize, &contig_vmo, 0));
zx::vmo pinned_vmo;
ASSERT_OK(zx::vmo::create(kSize, 0, &pinned_vmo));
zx_paddr_t paddrs[kPageCount];
zx::pmt pmt;
ASSERT_OK(bti.pin(ZX_BTI_PERM_READ, pinned_vmo, 0, kSize, paddrs, kPageCount, &pmt));
EXPECT_EQ(ZX_ERR_BAD_STATE, pinned_vmo.transfer_data(0, kPageSize, kPageSize, &src_vmo, 0));
EXPECT_EQ(ZX_ERR_BAD_STATE, dst_vmo.transfer_data(0, kPageSize, kPageSize, &pinned_vmo, 0));
ASSERT_OK(pmt.unpin());
}
} // namespace