blob: b46f3424b4e3e55dfb812a2b3670bb7cde214c63 [file] [edit]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "test_helper.h"
#include <lib/fit/defer.h>
#include <lib/page/size.h>
namespace vm_unittest {
// Helper function to allocate memory in a user address space.
zx_status_t AllocUser(VmAspace* aspace, const char* name, size_t size, user_inout_ptr<void>* ptr) {
ASSERT(aspace->is_user());
size = RoundUpPageSize(size);
if (size == 0) {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<VmObjectPaged> vmo;
zx_status_t status = VmObjectPaged::Create(PMM_ALLOC_FLAG_ANY, 0u, size, &vmo);
if (status != ZX_OK) {
return status;
}
vmo->set_name(name, strlen(name));
static constexpr const uint kArchFlags = kArchRwFlags | ARCH_MMU_FLAG_PERM_USER;
auto mapping = aspace->RootVmar()->CreateVmMapping(0, size, 0, 0, vmo, 0, kArchFlags, name);
if (mapping.is_error()) {
return mapping.status_value();
}
*ptr = make_user_inout_ptr(reinterpret_cast<void*>(mapping->base));
return ZX_OK;
}
zx_status_t make_partially_committed_pager_vmo(size_t num_pages, size_t committed_pages,
bool trap_dirty, bool resizable,
bool ignore_requests, vm_page_t** out_pages,
fbl::RefPtr<VmObjectPaged>* out_vmo) {
// Disable the scanner so we can safely submit our aux vmo and query pages without eviction
// happening.
AutoVmScannerDisable scanner_disable;
// Create a pager backed VMO and jump through some hoops to pre-fill pages for it so we do not
// actually take any page faults.
fbl::AllocChecker ac;
fbl::RefPtr<StubPageProvider> pager =
fbl::MakeRefCountedChecked<StubPageProvider>(&ac, trap_dirty, ignore_requests);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
fbl::RefPtr<PageSource> src = fbl::MakeRefCountedChecked<PageSource>(&ac, ktl::move(pager));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
fbl::RefPtr<VmObjectPaged> vmo;
zx_status_t status = VmObjectPaged::CreateExternal(
ktl::move(src), resizable ? VmObjectPaged::kResizable : 0, num_pages * kPageSize, &vmo);
if (status != ZX_OK) {
return status;
}
if (committed_pages > 0) {
fbl::RefPtr<VmObjectPaged> aux_vmo;
status = VmObjectPaged::Create(0, 0, committed_pages * kPageSize, &aux_vmo);
if (status != ZX_OK) {
return status;
}
status = aux_vmo->CommitRange(0, committed_pages * kPageSize);
if (status != ZX_OK) {
return status;
}
VmPageSpliceList splice_list;
status = aux_vmo->TakePages(0, committed_pages * kPageSize, &splice_list);
if (status != ZX_OK) {
return status;
}
status =
vmo->SupplyPages(0, committed_pages * kPageSize, &splice_list, SupplyOptions::PagerSupply);
if (status != ZX_OK) {
return status;
}
if (out_pages) {
for (uint64_t i = 0; i < committed_pages; i++) {
status = vmo->GetPage(i * kPageSize, 0, nullptr, nullptr, &out_pages[i], nullptr);
if (status != ZX_OK) {
return status;
}
}
}
}
*out_vmo = ktl::move(vmo);
return ZX_OK;
}
zx_status_t make_uncommitted_pager_vmo(size_t num_pages, bool trap_dirty, bool resizable,
fbl::RefPtr<VmObjectPaged>* out_vmo) {
return make_partially_committed_pager_vmo(num_pages, 0, trap_dirty, resizable, false, nullptr,
out_vmo);
}
zx_status_t make_committed_pager_vmo(size_t num_pages, bool trap_dirty, bool resizable,
vm_page_t** out_pages, fbl::RefPtr<VmObjectPaged>* out_vmo) {
return make_partially_committed_pager_vmo(num_pages, num_pages, trap_dirty, resizable, false,
out_pages, out_vmo);
}
zx_status_t supply_pager_vmo_pages(VmObjectPaged* vmo, uint64_t page_offset, uint64_t num_pages,
vm_page_t** out_pages) {
// Disable the scanner so we can safely submit our aux vmo and query pages without eviction
// happening.
AutoVmScannerDisable scanner_disable;
fbl::RefPtr<VmObjectPaged> aux_vmo;
zx_status_t status = VmObjectPaged::Create(0, 0, num_pages * kPageSize, &aux_vmo);
if (status != ZX_OK) {
return status;
}
status = aux_vmo->CommitRange(0, num_pages * kPageSize);
if (status != ZX_OK) {
return status;
}
VmPageSpliceList splice_list;
status = aux_vmo->TakePages(0, num_pages * kPageSize, &splice_list);
if (status != ZX_OK) {
return status;
}
status = vmo->SupplyPages(page_offset * kPageSize, num_pages * kPageSize, &splice_list,
SupplyOptions::PagerSupply);
if (status != ZX_OK) {
return status;
}
if (out_pages) {
for (uint64_t i = 0; i < num_pages; i++) {
status = vmo->GetPage((page_offset + i) * kPageSize, 0, nullptr, nullptr,
&out_pages[page_offset + i], nullptr);
if (status != ZX_OK) {
return status;
}
}
}
return ZX_OK;
}
uint32_t test_rand(uint32_t seed) { return (seed = seed * 1664525 + 1013904223); }
// fill a region of memory with a pattern based on the address of the region
void fill_region(uintptr_t seed, void* _ptr, size_t len) {
uint32_t* ptr = (uint32_t*)_ptr;
ASSERT(IS_ROUNDED((uintptr_t)ptr, 4));
uint32_t val = (uint32_t)seed;
val ^= (uint32_t)(seed >> 32);
for (size_t i = 0; i < len / 4; i++) {
ptr[i] = val;
val = test_rand(val);
}
}
// just like |fill_region|, but for user memory
void fill_region_user(uintptr_t seed, user_inout_ptr<void> _ptr, size_t len) {
user_inout_ptr<uint32_t> ptr = _ptr.reinterpret<uint32_t>();
ASSERT(IS_ROUNDED(ptr.get(), 4));
uint32_t val = (uint32_t)seed;
val ^= (uint32_t)(seed >> 32);
for (size_t i = 0; i < len / 4; i++) {
zx_status_t status = ptr.element_offset(i).copy_to_user(val);
ASSERT(status == ZX_OK);
val = test_rand(val);
}
}
// test a region of memory against a known pattern
bool test_region(uintptr_t seed, void* _ptr, size_t len) {
uint32_t* ptr = (uint32_t*)_ptr;
ASSERT(IS_ROUNDED((uintptr_t)ptr, 4));
uint32_t val = (uint32_t)seed;
val ^= (uint32_t)(seed >> 32);
for (size_t i = 0; i < len / 4; i++) {
if (ptr[i] != val) {
unittest_printf("value at %p (%zu) is incorrect: 0x%x vs 0x%x\n", &ptr[i], i, ptr[i], val);
return false;
}
val = test_rand(val);
}
return true;
}
// just like |test_region|, but for user memory
bool test_region_user(uintptr_t seed, user_inout_ptr<void> _ptr, size_t len) {
user_inout_ptr<uint32_t> ptr = _ptr.reinterpret<uint32_t>();
ASSERT(IS_ROUNDED(ptr.get(), 4));
uint32_t val = (uint32_t)seed;
val ^= (uint32_t)(seed >> 32);
for (size_t i = 0; i < len / 4; i++) {
auto p = ptr.element_offset(i);
uint32_t actual;
zx_status_t status = p.copy_from_user(&actual);
ASSERT(status == ZX_OK);
if (actual != val) {
unittest_printf("value at %p (%zu) is incorrect: 0x%x vs 0x%x\n", p.get(), i, actual, val);
return false;
}
val = test_rand(val);
}
return true;
}
bool fill_and_test(void* ptr, size_t len) {
BEGIN_TEST;
// fill it with a pattern
fill_region((uintptr_t)ptr, ptr, len);
// test that the pattern is read back properly
auto result = test_region((uintptr_t)ptr, ptr, len);
EXPECT_TRUE(result, "testing region for corruption");
END_TEST;
}
// just like |fill_and_test|, but for user memory
bool fill_and_test_user(user_inout_ptr<void> ptr, size_t len) {
BEGIN_TEST;
const auto seed = reinterpret_cast<uintptr_t>(ptr.get());
// fill it with a pattern
fill_region_user(seed, ptr, len);
// test that the pattern is read back properly
auto result = test_region_user(seed, ptr, len);
EXPECT_TRUE(result, "testing region for corruption");
END_TEST;
}
// Helper function used by vmo_mapping_page_fault_optimisation_test.
// Given a mapping, check that a run of consecutive pages are mapped (indicated by
// expected_mapped_page_count) and that remaining pages are unmapped.
bool verify_mapped_page_range(vaddr_t base, size_t mapping_size,
size_t expected_mapped_page_count) {
BEGIN_TEST;
// All pages up to expected_mapped_page_count should have been faulted.
paddr_t paddr_writable;
arch_mmu_flags_t mmu_flags;
uint64_t offset = 0;
for (; offset < expected_mapped_page_count * kPageSize; offset += kPageSize) {
ASSERT_OK(Thread::Current::Get()->active_aspace()->arch_aspace().Query(
base + offset, &paddr_writable, &mmu_flags));
EXPECT_TRUE(mmu_flags & ARCH_MMU_FLAG_PERM_READ);
}
// Remaining pages should not have been faulted in.
for (; offset < mapping_size; offset += kPageSize) {
ASSERT_EQ(Thread::Current::Get()->active_aspace()->arch_aspace().Query(
base + offset, &paddr_writable, &mmu_flags),
ZX_ERR_NOT_FOUND);
}
END_TEST;
}
bool verify_continuous_attribution_bytes(VmObject& vmo, uint64_t expected_bytes) {
BEGIN_TEST;
VmObjectPaged* paged = DownCastVmObject<VmObjectPaged>(&vmo);
ASSERT_NONNULL(paged, "only paged VMOs have a continuously tracked populated bytes count");
EXPECT_NE(VmObject::ChildType::kSlice, paged->child_type(),
"slice VMOs do not have a continuously tracked populated bytes count");
if constexpr (EXPERIMENTAL_CONTINUOUS_PER_VMO_ATTRIBUTION_ENABLED) {
const uint64_t tracked_slots = paged->DebugGetCowPages()->DebugGetPopulatedSlotsCount();
const uint64_t tracked_bytes = kPageSize * tracked_slots;
EXPECT_EQ(expected_bytes, tracked_bytes);
}
END_TEST;
}
VmObject::AttributionCounts make_private_attribution_counts(uint64_t uncompressed,
uint64_t compressed) {
return vm::AttributionCounts{
.uncompressed_bytes = uncompressed,
.compressed_bytes = compressed,
.private_uncompressed_bytes = uncompressed,
.private_compressed_bytes = compressed,
.scaled_uncompressed_bytes = vm::FractionalBytes(uncompressed),
.scaled_compressed_bytes = vm::FractionalBytes(compressed),
};
}
} // namespace vm_unittest