| // Copyright 2018 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/fake-bti/bti.h> |
| #include <lib/fzl/vmo-pool.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/rights.h> |
| |
| #include <utility> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "vmo-probe.h" |
| |
| // Things to test: |
| // 1) Init with vmos, init with non-initialized vmos |
| // 2) memset at address, for size() |
| // 3) Get a bunch of buffers, make sure it runs out |
| // 4) Call LockBufferForWrite twice, assert fail |
| // 5) pass bad buffer index to ReleaseBuffer |
| // 6) try to release twice |
| // 7) Check LockBufferForWrite and BufferCompleted return the same |
| |
| namespace { |
| |
| static constexpr size_t kVmoTestSize = 512 << 10; // 512KB |
| static constexpr uint32_t kNumVmos = 20; |
| |
| // Create vmos for each handle in an array of vmo handles: |
| void AssignVmos(size_t num_vmos, size_t vmo_size, zx::vmo* vmos) { |
| zx_status_t status; |
| for (size_t i = 0; i < num_vmos; ++i) { |
| status = zx::vmo::create(vmo_size, 0, &vmos[i]); |
| EXPECT_OK(status); |
| } |
| } |
| |
| // A helper class to initialize the VmoPool, and to check the state. |
| // Since we cannot access the VmoPool's free buffer list, we check the |
| // state of the VmoPool by filling it up and emptying it out. |
| class VmoPoolTester : public zxtest::Test { |
| public: |
| zx::vmo vmo_handles_[kNumVmos]; |
| fzl::VmoPool pool_; |
| bool is_mapped_ = false; |
| bool is_pinned_ = false; |
| |
| void Init() { |
| AssignVmos(kNumVmos, kVmoTestSize, vmo_handles_); |
| ASSERT_OK(pool_.Init(vmo_handles_, kNumVmos)); |
| } |
| |
| void SetUp() override { ASSERT_OK(fake_bti_create(bti_.reset_and_get_address())); } |
| |
| void FillBuffers(size_t num_buffers) { |
| for (size_t i = 0; i < kNumVmos && i < num_buffers; ++i) { |
| auto buffer = pool_.LockBufferForWrite(); |
| ASSERT_TRUE(buffer.has_value()); |
| __UNUSED uint32_t index = buffer->ReleaseWriteLockAndGetIndex(); |
| } |
| } |
| |
| // Create vmos for each handle in an array of vmo handles: |
| void CreateContiguousVmos(size_t num_vmos, size_t vmo_size, zx::vmo* vmos) { |
| for (size_t i = 0; i < num_vmos; ++i) { |
| zx_status_t status = zx::vmo::create_contiguous(bti_, vmo_size, 0, &vmos[i]); |
| ASSERT_OK(status); |
| } |
| } |
| |
| void InitContiguous() { |
| CreateContiguousVmos(kNumVmos, kVmoTestSize, vmo_handles_); |
| ASSERT_OK(pool_.Init(vmo_handles_, kNumVmos)); |
| } |
| |
| void PinVmos(fzl::VmoPool::RequireContig require_contiguous, |
| fzl::VmoPool::RequireLowMem require_low_memory) { |
| EXPECT_EQ(pool_.PinVmos(bti_, require_contiguous, require_low_memory), ZX_OK); |
| is_pinned_ = true; |
| } |
| |
| void MapVmos() { |
| EXPECT_OK(pool_.MapVmos()); |
| is_mapped_ = true; |
| } |
| |
| // Fills the pool, to make sure all accounting |
| // is done correctly. |
| // filled_count is the number of buffers that are already reserved. |
| void CheckFillingPool(size_t filled_count) { |
| // Test that the pool gives indexes from 0 to kNumVmos-1 |
| // It is not required to give the indexes in any particular |
| // order. |
| bool gave_index[kNumVmos]; // initialized to false |
| for (size_t i = 0; i < kNumVmos; ++i) { |
| gave_index[i] = false; |
| } |
| for (size_t i = 0; i < kNumVmos - filled_count; ++i) { |
| auto buffer = pool_.LockBufferForWrite(); |
| ASSERT_TRUE(buffer.has_value()); |
| CheckValidBuffer(*buffer); |
| uint32_t buffer_index = buffer->ReleaseWriteLockAndGetIndex(); |
| CheckInvalidBuffer(*buffer); |
| |
| ASSERT_LT(buffer_index, kNumVmos); |
| EXPECT_EQ(gave_index[buffer_index], false); |
| gave_index[buffer_index] = true; |
| } |
| // Now check that requesting any further buffers fails: |
| auto buffer = pool_.LockBufferForWrite(); |
| EXPECT_FALSE(buffer.has_value()); |
| } |
| |
| // Check the Buffer to make sure it gives the correct info |
| void CheckValidBuffer(fzl::VmoPool::Buffer& buffer) { |
| ASSERT_TRUE(buffer.valid()); |
| EXPECT_EQ(buffer.size(), kVmoTestSize); |
| if (is_mapped_) { |
| ASSERT_NO_DEATH(([&buffer] { EXPECT_NE(buffer.virtual_address(), nullptr); })); |
| } else { |
| ASSERT_DEATH(([&buffer] { buffer.virtual_address(); })); |
| } |
| if (is_pinned_) { |
| // Cannot assume that the physical address will be non-zero, since |
| // fake-bti returns physical addresses of 0. |
| ASSERT_NO_DEATH(([&buffer] { buffer.physical_address(); })); |
| } else { |
| ASSERT_DEATH(([&buffer] { buffer.physical_address(); })); |
| } |
| } |
| |
| // Check that an invalid buffer acts according to spec. |
| void CheckInvalidBuffer(fzl::VmoPool::Buffer& buffer) { |
| EXPECT_FALSE(buffer.valid()); |
| ASSERT_DEATH(([&buffer] { buffer.size(); })); |
| ASSERT_DEATH(([&buffer] { buffer.virtual_address(); })); |
| ASSERT_DEATH(([&buffer] { buffer.physical_address(); })); |
| } |
| // Empties the pool, to make sure all accounting |
| // is done correctly. |
| // unfilled_count is the number of buffers that are already reserved. |
| void CheckEmptyPool(size_t unfilled_count) { |
| size_t release_errors = 0; |
| for (uint32_t i = 0; i < kNumVmos; ++i) { |
| if (pool_.ReleaseBuffer(i) == ZX_ERR_NOT_FOUND) { |
| release_errors++; |
| EXPECT_LE(release_errors, unfilled_count); |
| } |
| } |
| // Make sure we had exactly filled_count buffers already released. |
| EXPECT_EQ(unfilled_count, release_errors); |
| // Now, make sure all buffers are now released. |
| for (uint32_t i = 0; i < kNumVmos; ++i) { |
| EXPECT_EQ(pool_.ReleaseBuffer(i), ZX_ERR_NOT_FOUND); |
| } |
| } |
| |
| void CheckAccounting(size_t filled_count) { |
| EXPECT_EQ(kNumVmos, pool_.total_buffers()); |
| EXPECT_EQ(kNumVmos - filled_count, pool_.free_buffers()); |
| CheckFillingPool(filled_count); |
| CheckEmptyPool(0); |
| EXPECT_EQ(kNumVmos, pool_.free_buffers()); |
| } |
| |
| // Shuffles the free list, psuedo-randomly. |
| // Assumes that the pool is empty. |
| // This shuffle function relies on the fact that if you have a prime |
| // number p and a number (n) that does not have that prime number |
| // as a factor, the set of (x*p)%n, where x := {0,n-1} will cover the |
| // range of {0,n-1} exactly. |
| void ShufflePool() { |
| FillBuffers(kNumVmos); |
| static constexpr uint32_t kHashingSeed = 7; |
| static_assert(kNumVmos % kHashingSeed != 0, "Bad Hashing seed"); |
| uint32_t hashing_index = 0; |
| for (size_t i = 0; i < kNumVmos; ++i) { |
| hashing_index = (hashing_index + kHashingSeed) % kNumVmos; |
| ASSERT_OK(pool_.ReleaseBuffer(hashing_index)); |
| } |
| } |
| |
| zx::bti bti_; |
| }; |
| |
| TEST_F(VmoPoolTester, FillAndEmptyPool) { |
| Init(); |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, FillAndEmptyPinnedPool) { |
| InitContiguous(); |
| CheckAccounting(0); |
| PinVmos(fzl::VmoPool::RequireContig::Yes, fzl::VmoPool::RequireLowMem::Yes); |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, FillAndEmptyMappedPool) { |
| Init(); |
| CheckAccounting(0); |
| MapVmos(); |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, NoncontigPinnedPool) { |
| Init(); |
| PinVmos(fzl::VmoPool::RequireContig::No, fzl::VmoPool::RequireLowMem::Yes); |
| } |
| |
| TEST_F(VmoPoolTester, DoubleGetBuffer) { |
| Init(); |
| auto buffer = pool_.LockBufferForWrite(); |
| EXPECT_TRUE(buffer.has_value()); |
| auto buffer2 = pool_.LockBufferForWrite(); |
| EXPECT_TRUE(buffer.has_value()); |
| |
| // Now check accounting: |
| CheckAccounting(2); |
| } |
| |
| // Checks that you can cancel a buffer, which will put it back into the pool. |
| TEST_F(VmoPoolTester, ReleaseBeforeComplete) { |
| Init(); |
| auto buffer = pool_.LockBufferForWrite(); |
| ASSERT_TRUE(buffer.has_value()); |
| EXPECT_OK(buffer->Release()); |
| |
| // Now check accounting: |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, ReleaseWrongBuffer) { |
| Init(); |
| |
| auto buffer = pool_.LockBufferForWrite(); |
| ASSERT_TRUE(buffer.has_value()); |
| ASSERT_TRUE(buffer->valid()); |
| uint32_t current_buffer = buffer->ReleaseWriteLockAndGetIndex(); |
| // Make sure that we can't mark complete twice: |
| ASSERT_DEATH(([&buffer]() { buffer->ReleaseWriteLockAndGetIndex(); })); |
| // Test an out-of-bounds index: |
| EXPECT_EQ(pool_.ReleaseBuffer(kNumVmos), ZX_ERR_INVALID_ARGS); |
| // Test all of the buffer indices that are not locked: |
| for (uint32_t i = 0; i < kNumVmos; ++i) { |
| if (i == current_buffer) { |
| continue; |
| } |
| EXPECT_EQ(pool_.ReleaseBuffer(i), ZX_ERR_NOT_FOUND); |
| } |
| // Now check accounting: |
| CheckAccounting(1); |
| } |
| |
| // Checks that the pool does not need to be amptied or filled in any particular |
| // order. |
| TEST_F(VmoPoolTester, OutOfOrder) { |
| Init(); |
| ShufflePool(); |
| // Now check accounting: |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, Reset) { |
| Init(); |
| size_t test_cases[]{0, 1, kNumVmos / 2, kNumVmos}; |
| for (size_t buffer_count : test_cases) { |
| // With no buffer in progress |
| FillBuffers(buffer_count); |
| pool_.Reset(); |
| CheckAccounting(0); |
| } |
| } |
| |
| TEST_F(VmoPoolTester, Reinit) { |
| Init(); |
| CheckAccounting(0); |
| |
| Init(); |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, StdMove) { |
| Init(); |
| fzl::VmoPool::Buffer source; |
| fzl::VmoPool::Buffer destination; |
| |
| destination = std::move(source); |
| |
| CheckAccounting(0); |
| } |
| |
| TEST_F(VmoPoolTester, BufferSize) { |
| Init(); |
| |
| #if ZX_DEBUG_ASSERT_IMPLEMENTED |
| ASSERT_DEATH([&] { pool_.buffer_size(-1); }); |
| ASSERT_DEATH([&] { pool_.buffer_size(kNumVmos); }); |
| #endif |
| |
| ASSERT_EQ(kVmoTestSize, pool_.buffer_size()); |
| ASSERT_EQ(kVmoTestSize, pool_.buffer_size(kNumVmos - 1)); |
| CheckAccounting(0); |
| } |
| } // namespace |