blob: 1196920bb72dce794fb2761dcf12312146444d33 [file] [log] [blame]
// Copyright 2019 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 <assert.h>
#include <lib/zx/bti.h>
#include <lib/zx/iommu.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <zircon/syscalls/iommu.h>
#include <zxtest/zxtest.h>
#include "helpers.h"
extern "C" __WEAK zx_handle_t get_root_resource(void);
namespace vmo_test {
bool AllSameVal(uint8_t *ptr, size_t len, uint8_t val) {
for (size_t i = 0; i < len; i++) {
if (ptr[i] != val) {
return false;
}
}
return true;
}
TEST(VmoZeroTestCase, UnalignedSubPage) {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
Mapping mapping;
EXPECT_OK(mapping.Init(vmo, PAGE_SIZE));
uint8_t *ptr = mapping.bytes();
memset(ptr, 0xff, PAGE_SIZE);
// zero a few words in the middle of the page.
EXPECT_OK(vmo.op_range(ZX_VMO_OP_ZERO, 42, 91, NULL, 0));
EXPECT_TRUE(AllSameVal(ptr, 42, 0xff));
EXPECT_TRUE(AllSameVal(ptr + 42, 91, 0));
EXPECT_TRUE(AllSameVal(ptr + 42 + 91, PAGE_SIZE - 42 - 91, 0xff));
}
TEST(VmoZeroTestCase, UnalignedCommitted) {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &vmo));
Mapping mapping;
EXPECT_OK(mapping.Init(vmo, PAGE_SIZE * 2));
uint8_t *ptr = mapping.bytes();
memset(ptr, 0xff, PAGE_SIZE * 2);
// zero across both page boundaries
EXPECT_OK(vmo.op_range(ZX_VMO_OP_ZERO, PAGE_SIZE / 2, PAGE_SIZE, NULL, 0));
EXPECT_TRUE(AllSameVal(ptr, PAGE_SIZE / 2, 0xff));
EXPECT_TRUE(AllSameVal(ptr + PAGE_SIZE / 2, PAGE_SIZE, 0));
EXPECT_TRUE(AllSameVal(ptr + PAGE_SIZE + PAGE_SIZE / 2, PAGE_SIZE / 2, 0xff));
}
TEST(VmoZeroTestCase, UnalignedUnCommitted) {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &vmo));
EXPECT_EQ(0, VmoCommittedBytes(vmo));
// zero across both page boundaries. As these are already known zero pages this should not reuslt
// in any pages being committed.
EXPECT_OK(vmo.op_range(ZX_VMO_OP_ZERO, PAGE_SIZE / 2, PAGE_SIZE, NULL, 0));
EXPECT_EQ(0, VmoCommittedBytes(vmo));
}
TEST(VmoZeroTestCase, DecommitMiddle) {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 3, 0, &vmo));
Mapping mapping;
EXPECT_OK(mapping.Init(vmo, PAGE_SIZE * 3));
uint8_t *ptr = mapping.bytes();
memset(ptr, 0xff, PAGE_SIZE * 3);
EXPECT_EQ(PAGE_SIZE * 3, VmoCommittedBytes(vmo));
// zero across all three pages. This should decommit the middle one.
EXPECT_OK(vmo.op_range(ZX_VMO_OP_ZERO, PAGE_SIZE / 2, PAGE_SIZE * 2, NULL, 0));
// Only two pages should be committed
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(vmo));
}
TEST(VmoZeroTestCase, Contiguous) {
if (!get_root_resource) {
printf("Root resource not available, skipping\n");
return;
}
zx::unowned_resource root_res(get_root_resource());
zx::iommu iommu;
zx::bti bti;
zx_iommu_desc_dummy_t desc;
EXPECT_OK(zx::iommu::create(*root_res, ZX_IOMMU_TYPE_DUMMY, &desc, sizeof(desc), &iommu));
EXPECT_OK(zx::bti::create(iommu, 0, 0xdeadbeef, &bti));
zx::vmo vmo;
EXPECT_OK(zx::vmo::create_contiguous(bti, PAGE_SIZE * 2, 0, &vmo));
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(vmo));
// Pin momentarily to retrieve the physical address
zx_paddr_t phys_addr;
{
zx::pmt pmt;
EXPECT_OK(
bti.pin(ZX_BTI_PERM_WRITE | ZX_BTI_CONTIGUOUS, vmo, 0, PAGE_SIZE * 2, &phys_addr, 1, &pmt));
pmt.unpin();
}
Mapping mapping;
EXPECT_OK(mapping.Init(vmo, PAGE_SIZE * 2));
uint8_t *ptr = mapping.bytes();
memset(ptr, 0xff, PAGE_SIZE * 2);
// Zero a page. should not cause decommit as our VMO must remain contiguous.
EXPECT_OK(vmo.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(vmo));
EXPECT_TRUE(AllSameVal(ptr, PAGE_SIZE, 0));
EXPECT_TRUE(AllSameVal(ptr + PAGE_SIZE, PAGE_SIZE, 0xff));
// Pin again to make sure physical contiguity was preserved.
zx_paddr_t phys_addr2;
{
zx::pmt pmt;
EXPECT_OK(bti.pin(ZX_BTI_PERM_WRITE | ZX_BTI_CONTIGUOUS, vmo, 0, PAGE_SIZE * 2, &phys_addr2, 1,
&pmt));
pmt.unpin();
}
EXPECT_EQ(phys_addr, phys_addr2);
}
TEST(VmoZeroTestCase, EmptyCowChildren) {
zx::vmo parent;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &parent));
// Commit the first page by writing to it.
VmoWrite(parent, 1, 0);
zx::vmo child;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 2, &child));
// Parent should have the page currently attributed to it.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Validate child contents.
VmoCheck(child, 1, 0);
// Zero the child. Should not change pages committed, but child should now read as 0.
EXPECT_OK(child.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
VmoCheck(child, 0, 0);
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Now zero the parent. Should be no need to keep the underlying pages around, dropping committed.
EXPECT_OK(parent.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
VmoCheck(parent, 0, 0);
EXPECT_EQ(0, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
}
TEST(VmoZeroTestCase, MergeZeroChildren) {
zx::vmo parent;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &parent));
EXPECT_OK(parent.op_range(ZX_VMO_OP_COMMIT, 0, PAGE_SIZE, NULL, 0));
zx::vmo child;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE, &child));
// Parent should have the page currently attributed to it.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Zero the parent. Pages should move to the child.
EXPECT_OK(parent.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
EXPECT_EQ(0, VmoCommittedBytes(parent));
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child));
// Close the child. Pages should cease being committed and not move to the parent.
child.reset();
EXPECT_EQ(0, VmoCommittedBytes(parent));
}
// Tests that after merging a child with its hidden parent that hidden pages are correctly preserved
// and do not get replaced by hidden parents pages.
TEST(VmoZeroTestCase, AllocateAfterMerge) {
zx::vmo parent;
InitPageTaggedVmo(2, &parent);
zx::vmo child;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 2, &child));
// Validate initial state.
VmoCheck(child, 1, 0);
VmoCheck(child, 2, PAGE_SIZE);
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(parent) + VmoCommittedBytes(child));
// Zero first page of the child. This doesn't change number of pages committed as our sibling
// is still using it.
EXPECT_OK(child.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(parent) + VmoCommittedBytes(child));
// Close the parent to make the merge happen.
parent.reset();
// Should only have 1 page attributed to us, and reading should still give us our expected pages
// and not those of our merge partner.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child));
VmoCheck(child, 0, 0);
VmoCheck(child, 2, PAGE_SIZE);
}
// Similar to AllocateAfterMerge, but by merging with a hidden child we will hit the non fast_merge
// path in the kernel.
TEST(VmoZeroTestCase, AllocateAfterMergeHiddenChild) {
zx::vmo parent;
InitPageTaggedVmo(3, &parent);
zx::vmo child1, child2;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 3, &child1));
EXPECT_EQ(PAGE_SIZE * 3, VmoCommittedBytes(parent) + VmoCommittedBytes(child1));
// Zero a page in the parent before creating the next child. This places a zero page in the
// common hidden parent.
EXPECT_OK(parent.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
EXPECT_EQ(PAGE_SIZE * 3, VmoCommittedBytes(parent) + VmoCommittedBytes(child1));
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 3, &child2));
// Zero the middle page of child1. This leaves the number of comitted pages the same.
EXPECT_OK(child1.op_range(ZX_VMO_OP_ZERO, PAGE_SIZE, PAGE_SIZE, NULL, 0));
EXPECT_EQ(PAGE_SIZE * 3,
VmoCommittedBytes(parent) + VmoCommittedBytes(child1) + VmoCommittedBytes(child2));
// Validate page states.
VmoCheck(child2, 0, 0);
VmoCheck(child2, 2, PAGE_SIZE);
VmoCheck(child2, 3, PAGE_SIZE * 2);
EXPECT_EQ(PAGE_SIZE * 3,
VmoCommittedBytes(parent) + VmoCommittedBytes(child1) + VmoCommittedBytes(child2));
// Close the first child, forcing that hidden parent to merge with the hidden parent of parent and
// child2. Child1's zero page should be discarded and not overwrite the forked version, and the
// page we zeroed in the parent should also not get overridden.
VmoCheck(child1, 1, 0);
VmoCheck(child1, 0, PAGE_SIZE);
VmoCheck(child1, 3, PAGE_SIZE * 2);
child1.reset();
VmoCheck(parent, 0, 0);
VmoCheck(parent, 2, PAGE_SIZE);
VmoCheck(parent, 3, PAGE_SIZE * 2);
VmoCheck(child2, 0, 0);
VmoCheck(child2, 2, PAGE_SIZE);
VmoCheck(child2, 3, PAGE_SIZE * 2);
EXPECT_EQ(PAGE_SIZE * 2, VmoCommittedBytes(parent) + VmoCommittedBytes(child2));
// Write to a different byte in our zero page to see if we can uncover child1's data.
VmoWrite(parent, 1, 64);
VmoCheck(parent, 0, 0);
EXPECT_EQ(PAGE_SIZE * 3, VmoCommittedBytes(parent) + VmoCommittedBytes(child2));
// Fork the middle page that child1 zeroed and ensure we CoW the correct underlying page.
VmoWrite(child2, 5, PAGE_SIZE + 64);
VmoCheck(child2, 2, PAGE_SIZE);
VmoCheck(parent, 0, PAGE_SIZE + 64);
VmoCheck(parent, 2, PAGE_SIZE);
EXPECT_EQ(PAGE_SIZE * 4, VmoCommittedBytes(parent) + VmoCommittedBytes(child2));
}
TEST(VmoZeroTestCase, WriteCowParent) {
zx::vmo parent;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &parent));
VmoWrite(parent, 1, 0);
zx::vmo child;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 2, &child));
// Parent should have the page currently attributed to it.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Write to the parent to perform a COW copy.
VmoCheck(parent, 1, 0);
VmoWrite(parent, 2, 0);
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child));
// Zero the child. This should decommit the child page.
VmoCheck(child, 1, 0);
EXPECT_OK(child.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
VmoCheck(child, 0, 0);
VmoCheck(parent, 2, 0);
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Close the parent. No pages should get merged.
parent.reset();
VmoCheck(child, 0, 0);
EXPECT_EQ(0, VmoCommittedBytes(child));
}
TEST(VmoZeroTestCase, ChildZeroThenWrite) {
zx::vmo parent;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &parent));
VmoWrite(parent, 1, 0);
zx::vmo child;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE * 2, &child));
// Parent should have the page currently attributed to it.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
EXPECT_OK(child.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
// Page attribution should be unchanged.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child));
// Write to the child, should cause a new page allocation.
VmoWrite(child, 1, 0);
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child));
// Reset the parent. The two committed pages should be different, and the parents page should be
// dropped.
parent.reset();
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child));
}
TEST(VmoZeroTestCase, Nested) {
zx::vmo parent;
EXPECT_OK(zx::vmo::create(PAGE_SIZE * 2, 0, &parent));
EXPECT_OK(parent.op_range(ZX_VMO_OP_COMMIT, 0, PAGE_SIZE, NULL, 0));
// Create two children.
zx::vmo child1, child2;
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE, &child1));
EXPECT_OK(parent.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, PAGE_SIZE, &child2));
// Should have 1 page total attributed to the parent.
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(parent));
EXPECT_EQ(0, VmoCommittedBytes(child1));
EXPECT_EQ(0, VmoCommittedBytes(child2));
// Zero the parent, this will cause the page to have to get forked down the intermediate hidden
// nodes.
EXPECT_OK(parent.op_range(ZX_VMO_OP_ZERO, 0, PAGE_SIZE, NULL, 0));
EXPECT_EQ(0, VmoCommittedBytes(parent));
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child1));
EXPECT_EQ(PAGE_SIZE, VmoCommittedBytes(child2));
}
} // namespace vmo_test