blob: 1590560a0d14905987cf455dad78e5b09cce721f [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 <fuchsia/camera2/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/fzl/memory-probe.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/component_context.h>
#include <vector>
#include <gtest/gtest.h>
#include <src/lib/syslog/cpp/logger.h>
namespace camera {
namespace {
constexpr uint32_t kCampingBufferCount = 2;
// If enabled, verify all mapped pages. If disabled, verifies first and last page only.
// TODO(fxb/42252): support memory-probe range checks
constexpr bool kEnableFullMemoryCheck = false;
class BufferLeakTest : public gtest::TestLoopFixture {
protected:
void SetUp() override {
context_ = sys::ComponentContext::Create();
ASSERT_EQ(context_->svc()->Connect(allocator_.NewRequest()), ZX_OK);
allocator_.set_error_handler(MakeErrorHandler("Sysmem Allocator"));
RunLoopUntilIdle();
}
void TearDown() override {
allocator_ = nullptr;
context_ = nullptr;
RunLoopUntilIdle();
}
static std::vector<fuchsia::camera2::StreamConstraints> GetStreamConstraints() {
fuchsia::camera2::CameraStreamType types[] = {
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION,
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING |
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION,
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING |
fuchsia::camera2::CameraStreamType::DOWNSCALED_RESOLUTION,
fuchsia::camera2::CameraStreamType::MONITORING,
fuchsia::camera2::CameraStreamType::VIDEO_CONFERENCE |
fuchsia::camera2::CameraStreamType::MACHINE_LEARNING |
fuchsia::camera2::CameraStreamType::FULL_RESOLUTION,
};
std::vector<fuchsia::camera2::StreamConstraints> constraints_ret;
for (auto type : types) {
fuchsia::camera2::StreamProperties properties;
properties.set_stream_type(type);
fuchsia::camera2::StreamConstraints constraints;
constraints.set_properties(std::move(properties));
constraints_ret.push_back(std::move(constraints));
}
return constraints_ret;
}
static fuchsia::sysmem::BufferCollectionConstraints GetCollectionConstraints() {
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.min_buffer_count_for_camping = kCampingBufferCount;
constraints.image_format_constraints_count = 0;
constraints.usage.cpu = fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
return constraints;
}
static fit::function<void(zx_status_t status)> MakeErrorHandler(std::string server) {
return [server](zx_status_t status) {
ADD_FAILURE() << server << " server disconnected - " << status;
};
}
// Verify whether the process has access to the memory reported by buffers consistent with format.
static void VerifyAccess(const fuchsia::sysmem::BufferCollectionInfo_2& buffers,
const fuchsia::sysmem::ImageFormat_2& format) {
fzl::VmoMapper mapper;
for (uint32_t i = 0; i < buffers.buffer_count; ++i) {
uint32_t bad_page_reads = 0;
uint32_t bad_page_writes = 0;
ASSERT_EQ(mapper.Map(buffers.buffers[i].vmo, buffers.buffers[i].vmo_usable_start,
buffers.settings.buffer_settings.size_bytes,
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE),
ZX_OK);
if (kEnableFullMemoryCheck) {
for (uint32_t row = 0; row < format.coded_height; ++row) {
for (uint32_t offset = 0; offset < format.bytes_per_row; offset += PAGE_SIZE) {
char* test_addr = static_cast<char*>(mapper.start()) + offset;
if (!probe_for_read(test_addr)) {
++bad_page_reads;
}
if (!probe_for_write(test_addr)) {
++bad_page_writes;
}
}
}
} else {
char* addr_first = static_cast<char*>(mapper.start());
char* addr_last = addr_first + format.bytes_per_row * format.coded_height - 1;
if (!probe_for_read(addr_first) || !probe_for_read(addr_last)) {
++bad_page_reads;
}
if (!probe_for_write(addr_first) || !probe_for_write(addr_last)) {
++bad_page_writes;
}
}
EXPECT_EQ(bad_page_reads, 0u) << "Buffer " << i << " memory not readable";
EXPECT_EQ(bad_page_writes, 0u) << "Buffer " << i << " memory not writable";
mapper.Unmap();
}
}
// Attempts to connect to the camera stream using the given constraints. Sets oom_out to true if
// sysmem reports oom during allocation. Returns the allocated buffers in buffers_out.
// Note that these are out params instead of return values due to the GTEST macros requiring
// methods to return void.
void TryConnect(fuchsia::camera2::StreamConstraints stream_constraints, bool* oom_out,
fuchsia::sysmem::BufferCollectionInfo_2* buffers_out) {
ZX_ASSERT(oom_out);
ZX_ASSERT(buffers_out);
*oom_out = false;
*buffers_out = fuchsia::sysmem::BufferCollectionInfo_2{};
fuchsia::camera2::ManagerPtr manager;
ASSERT_EQ(context_->svc()->Connect(manager.NewRequest()), ZX_OK);
manager.set_error_handler(MakeErrorHandler("Camera Manager"));
fuchsia::sysmem::BufferCollectionTokenPtr token;
allocator_->AllocateSharedCollection(token.NewRequest());
token.set_error_handler(MakeErrorHandler("Token"));
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> manager_token;
token->Duplicate(ZX_RIGHT_SAME_RIGHTS, manager_token.NewRequest());
fuchsia::sysmem::BufferCollectionPtr collection;
allocator_->BindSharedCollection(std::move(token), collection.NewRequest());
collection.set_error_handler(MakeErrorHandler("Collection"));
collection->SetConstraints(true, GetCollectionConstraints());
bool sync_returned = false;
collection->Sync([&]() { sync_returned = true; });
while (!sync_returned) {
RunLoopUntilIdle();
}
fuchsia::camera2::StreamPtr stream;
fuchsia::sysmem::ImageFormat_2 format_ret;
bool connect_returned = false;
manager->ConnectToStream(0, std::move(stream_constraints), std::move(manager_token),
stream.NewRequest(), [&](fuchsia::sysmem::ImageFormat_2 format) {
format_ret = std::move(format);
connect_returned = true;
});
fuchsia::sysmem::BufferCollectionInfo_2 buffers_ret;
bool wait_returned = false;
collection->WaitForBuffersAllocated(
[&](zx_status_t status, fuchsia::sysmem::BufferCollectionInfo_2 buffers) {
wait_returned = true;
if (status == ZX_ERR_NO_MEMORY) {
*oom_out = true;
} else {
ASSERT_EQ(status, ZX_OK) << "Failed to allocate buffers";
}
buffers_ret = std::move(buffers);
});
while (!wait_returned || !connect_returned) {
RunLoopUntilIdle();
}
if (*oom_out) {
return;
}
collection->Close();
RunLoopUntilIdle();
ASSERT_GE(buffers_ret.buffer_count, kCampingBufferCount);
VerifyAccess(buffers_ret, format_ret);
bool frame_received = false;
stream.events().OnFrameAvailable = [&](fuchsia::camera2::FrameAvailableInfo info) {
stream->ReleaseFrame(info.buffer_id);
frame_received = true;
};
stream->Start();
while (!frame_received) {
RunLoopUntilIdle();
}
collection = nullptr;
stream = nullptr;
RunLoopUntilIdle();
*buffers_out = std::move(buffers_ret);
}
std::unique_ptr<sys::ComponentContext> context_;
fuchsia::sysmem::AllocatorPtr allocator_;
};
// TODO(fxb/42607): During the execution of this test, netstack occasionally
// fails when attempting to allocate buffers, causing it to fall over and crash.
// This causes the device to fall into a bad state in test harnesses, so further
// investigation needs to be done to allow this test to coexist with netstack.
TEST_F(BufferLeakTest, DISABLED_RepeatConnections) {
// Repeatedly connect to streams until the total memory allocated is at least twice the size of
// all physical memory.
uint64_t allocation_sum = 0;
uint64_t allocation_sum_target = zx_system_get_physmem() * 2;
for (uint32_t i = 0; allocation_sum < allocation_sum_target; ++i) {
for (auto& constraints : GetStreamConstraints()) {
uint32_t percent_complete = (allocation_sum * 100) / allocation_sum_target;
std::cout << "\rAllocated " << allocation_sum << " of " << allocation_sum_target << " bytes ("
<< percent_complete << "%)";
std::cout.flush();
bool oom_ret = false;
fuchsia::sysmem::BufferCollectionInfo_2 buffers_ret{};
TryConnect(std::move(constraints), &oom_ret, &buffers_ret);
ASSERT_FALSE(HasFatalFailure());
ASSERT_FALSE(oom_ret) << "SYSMEM OUT OF MEMORY at iteration " << i << " after allocating "
<< allocation_sum << " bytes";
allocation_sum += buffers_ret.buffer_count * buffers_ret.settings.buffer_settings.size_bytes;
}
}
std::cout << std::endl;
}
} // namespace
} // namespace camera