| // 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 "contiguous_pooled_memory_allocator.h" |
| |
| #include <lib/fake-bti/bti.h> |
| #include <lib/inspect/cpp/reader.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/vmar.h> |
| |
| #include <vector> |
| |
| #include <ddk/platform-defs.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace sysmem_driver { |
| namespace { |
| |
| class FakeOwner : public MemoryAllocator::Owner { |
| public: |
| FakeOwner() { EXPECT_OK(fake_bti_create(bti_.reset_and_get_address())); } |
| |
| ~FakeOwner() {} |
| |
| const zx::bti& bti() override { return bti_; } |
| zx_status_t CreatePhysicalVmo(uint64_t base, uint64_t size, zx::vmo* vmo_out) override { |
| return zx::vmo::create(size, 0u, vmo_out); |
| } |
| |
| private: |
| zx::bti bti_; |
| }; |
| |
| class ContiguousPooledSystem : public zxtest::Test { |
| public: |
| ContiguousPooledSystem() |
| : allocator_(&fake_owner_, kVmoName, &inspector_.GetRoot(), 0u, kVmoSize * kVmoCount, |
| true, // is_cpu_accessible |
| false, // is_ready |
| true) { // can_be_torn_down |
| // nothing else to do here |
| } |
| |
| protected: |
| static constexpr uint32_t kVmoSize = 4096; |
| static constexpr uint32_t kVmoCount = 1024; |
| static constexpr char kVmoName[] = "test-pool"; |
| |
| FakeOwner fake_owner_; |
| inspect::Inspector inspector_; |
| ContiguousPooledMemoryAllocator allocator_; |
| }; |
| |
| TEST_F(ContiguousPooledSystem, VmoNamesAreSet) { |
| EXPECT_OK(allocator_.Init()); |
| allocator_.set_ready(); |
| |
| char name[ZX_MAX_NAME_LEN] = {}; |
| EXPECT_OK(allocator_.GetPoolVmoForTest().get_property(ZX_PROP_NAME, name, sizeof(name))); |
| EXPECT_EQ(0u, strcmp(kVmoName, name)); |
| |
| zx::vmo vmo; |
| EXPECT_OK(allocator_.Allocate(kVmoSize, {}, &vmo)); |
| EXPECT_OK(vmo.get_property(ZX_PROP_NAME, name, sizeof(name))); |
| EXPECT_EQ(0u, strcmp("test-pool-child", name)); |
| allocator_.Delete(std::move(vmo)); |
| } |
| |
| TEST_F(ContiguousPooledSystem, Full) { |
| EXPECT_OK(allocator_.Init()); |
| allocator_.set_ready(); |
| |
| auto hierarchy = inspect::ReadFromVmo(inspector_.DuplicateVmo()); |
| auto* value = hierarchy.value().GetByPath({"test-pool"}); |
| ASSERT_TRUE(value); |
| |
| std::vector<zx::vmo> vmos; |
| for (uint32_t i = 0; i < kVmoCount; ++i) { |
| zx::vmo vmo; |
| EXPECT_OK(allocator_.Allocate(kVmoSize, {}, &vmo)); |
| vmos.push_back(std::move(vmo)); |
| } |
| |
| EXPECT_EQ(0u, value->node() |
| .get_property<inspect::UintPropertyValue>("last_allocation_failed_timestamp_ns") |
| ->value()); |
| auto before_time = zx::clock::get_monotonic(); |
| zx::vmo vmo; |
| EXPECT_NOT_OK(allocator_.Allocate(kVmoSize, {}, &vmo)); |
| |
| auto after_time = zx::clock::get_monotonic(); |
| |
| hierarchy = inspect::ReadFromVmo(inspector_.DuplicateVmo()); |
| value = hierarchy.value().GetByPath({"test-pool"}); |
| EXPECT_LE(before_time.get(), |
| value->node() |
| .get_property<inspect::UintPropertyValue>("last_allocation_failed_timestamp_ns") |
| ->value()); |
| EXPECT_GE(after_time.get(), |
| value->node() |
| .get_property<inspect::UintPropertyValue>("last_allocation_failed_timestamp_ns") |
| ->value()); |
| |
| allocator_.Delete(std::move(vmos[0])); |
| |
| EXPECT_OK(allocator_.Allocate(kVmoSize, {}, &vmos[0])); |
| |
| // Destroy half of all vmos. |
| for (uint32_t i = 0; i < kVmoCount; i += 2) { |
| ZX_DEBUG_ASSERT(vmos[i]); |
| allocator_.Delete(std::move(vmos[i])); |
| } |
| |
| // There shouldn't be enough contiguous address space for even 1 extra byte. |
| // This check relies on sequential Allocate() calls to a brand-new allocator |
| // being laid out sequentially, so isn't a fundamental check - if the |
| // allocator's layout strategy changes this check might start to fail |
| // without there necessarily being a real problem. |
| EXPECT_NOT_OK(allocator_.Allocate(kVmoSize + 1, {}, &vmo)); |
| |
| // This allocation should fail because there's not enough space in the pool, with or without |
| // fragmentation.: |
| EXPECT_NOT_OK(allocator_.Allocate(kVmoSize * kVmoCount - 1, {}, &vmo)); |
| |
| hierarchy = inspect::ReadFromVmo(inspector_.DuplicateVmo()); |
| value = hierarchy.value().GetByPath({"test-pool"}); |
| EXPECT_EQ(3u, |
| value->node().get_property<inspect::UintPropertyValue>("allocations_failed")->value()); |
| EXPECT_EQ(1u, value->node() |
| .get_property<inspect::UintPropertyValue>("allocations_failed_fragmentation") |
| ->value()); |
| // All memory was used at high water. |
| EXPECT_EQ( |
| 0u, |
| value->node().get_property<inspect::UintPropertyValue>("max_free_at_high_water")->value()); |
| for (auto& vmo : vmos) { |
| if (vmo) |
| allocator_.Delete(std::move(vmo)); |
| } |
| } |
| |
| TEST_F(ContiguousPooledSystem, GetPhysicalMemoryInfo) { |
| EXPECT_OK(allocator_.Init()); |
| allocator_.set_ready(); |
| |
| zx_paddr_t base; |
| size_t size; |
| ASSERT_OK(allocator_.GetPhysicalMemoryInfo(&base, &size)); |
| EXPECT_EQ(base, FAKE_BTI_PHYS_ADDR); |
| EXPECT_EQ(size, kVmoSize * kVmoCount); |
| } |
| |
| TEST_F(ContiguousPooledSystem, InitPhysical) { |
| // Using fake-bti and the FakeOwner above, it won't be a real physical VMO anyway. |
| EXPECT_OK(allocator_.InitPhysical(FAKE_BTI_PHYS_ADDR)); |
| allocator_.set_ready(); |
| |
| zx_paddr_t base; |
| size_t size; |
| ASSERT_OK(allocator_.GetPhysicalMemoryInfo(&base, &size)); |
| EXPECT_EQ(base, FAKE_BTI_PHYS_ADDR); |
| EXPECT_EQ(size, kVmoSize * kVmoCount); |
| |
| zx::vmo vmo; |
| EXPECT_OK(allocator_.Allocate(kVmoSize, {}, &vmo)); |
| allocator_.Delete(std::move(vmo)); |
| } |
| |
| TEST_F(ContiguousPooledSystem, SetReady) { |
| EXPECT_OK(allocator_.Init()); |
| EXPECT_FALSE(allocator_.is_ready()); |
| zx::vmo vmo; |
| EXPECT_EQ(ZX_ERR_BAD_STATE, allocator_.Allocate(kVmoSize, {}, &vmo)); |
| allocator_.set_ready(); |
| EXPECT_TRUE(allocator_.is_ready()); |
| EXPECT_OK(allocator_.Allocate(kVmoSize, {}, &vmo)); |
| allocator_.Delete(std::move(vmo)); |
| } |
| |
| } // namespace |
| } // namespace sysmem_driver |