| // 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 "device.h" |
| |
| #include <fidl/fuchsia.sysmem/cpp/fidl.h> |
| #include <fidl/fuchsia.sysmem/cpp/natural_types.h> |
| #include <fidl/fuchsia.sysmem2/cpp/fidl.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fidl/cpp/wire/arena.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zx/bti.h> |
| #include <stdlib.h> |
| #include <zircon/errors.h> |
| |
| #include <ddktl/device.h> |
| #include <ddktl/unbind-txn.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "../buffer_collection.h" |
| #include "../device.h" |
| #include "../driver.h" |
| #include "../logical_buffer_collection.h" |
| #include "src/devices/bus/testing/fake-pdev/fake-pdev.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace sysmem_driver { |
| namespace { |
| |
| TEST(Device, OverrideCommandLine) { |
| sysmem_driver::Driver sysmem_ctx; |
| std::shared_ptr<MockDevice> fake_parent = MockDevice::FakeRootParent(); |
| sysmem_driver::Device sysmem{fake_parent.get(), &sysmem_ctx}; |
| |
| const char* kCommandLine = "test.device.commandline"; |
| |
| int64_t value; |
| zx_status_t status; |
| |
| value = 10; |
| fake_parent->SetVariable(kCommandLine, "5"); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_OK(status); |
| EXPECT_EQ(5, value); |
| |
| value = 11; |
| fake_parent->SetVariable(kCommandLine, "65537"); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_OK(status); |
| EXPECT_EQ(65537, value); |
| |
| // Trailing characters should cause the entire value to be ignored. |
| value = 12; |
| fake_parent->SetVariable(kCommandLine, "65536a"); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_EQ(ZX_ERR_INVALID_ARGS, status); |
| EXPECT_EQ(12, value); |
| |
| // Empty values should be ignored. |
| value = 13; |
| fake_parent->SetVariable(kCommandLine, ""); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_OK(status); |
| EXPECT_EQ(13, value); |
| |
| // Negative values are allowed (these get interpreted as a percentage of physical RAM), but only |
| // up to 99% is allowed. |
| value = 14; |
| fake_parent->SetVariable(kCommandLine, "-100"); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_EQ(ZX_ERR_INVALID_ARGS, status); |
| EXPECT_EQ(14, value); |
| |
| value = 15; |
| fake_parent->SetVariable(kCommandLine, "-99"); |
| status = sysmem.OverrideSizeFromCommandLine(kCommandLine, &value); |
| EXPECT_OK(status); |
| EXPECT_EQ(-99, value); |
| } |
| |
| TEST(Device, GuardPageCommandLine) { |
| sysmem_driver::Driver sysmem_ctx; |
| std::shared_ptr<MockDevice> fake_parent = MockDevice::FakeRootParent(); |
| sysmem_driver::Device sysmem{fake_parent.get(), &sysmem_ctx}; |
| |
| uint64_t guard_bytes = 1; |
| bool unused_pages_guarded = true; |
| zx::duration unused_page_check_cycle_period; |
| bool internal_guard_pages = true; |
| bool crash_on_fail = true; |
| const char* kName = "driver.sysmem.contiguous_guard_page_count"; |
| const char* kInternalName = "driver.sysmem.contiguous_guard_pages_internal"; |
| |
| fake_parent->SetVariable(kInternalName, ""); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size(), guard_bytes); |
| EXPECT_TRUE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_TRUE(internal_guard_pages); |
| EXPECT_FALSE(crash_on_fail); |
| fake_parent->SetVariable(kInternalName, nullptr); |
| |
| fake_parent->SetVariable(kName, "fasfas"); |
| EXPECT_EQ(ZX_ERR_INVALID_ARGS, |
| sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size(), guard_bytes); |
| EXPECT_TRUE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_FALSE(internal_guard_pages); |
| EXPECT_FALSE(crash_on_fail); |
| |
| fake_parent->SetVariable(kName, ""); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size(), guard_bytes); |
| EXPECT_TRUE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_FALSE(internal_guard_pages); |
| EXPECT_FALSE(crash_on_fail); |
| |
| fake_parent->SetVariable(kName, "2"); |
| fake_parent->SetVariable(kInternalName, ""); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, guard_bytes); |
| EXPECT_TRUE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_TRUE(internal_guard_pages); |
| EXPECT_FALSE(crash_on_fail); |
| |
| const char* kFatalName = "driver.sysmem.contiguous_guard_pages_fatal"; |
| fake_parent->SetVariable(kFatalName, ""); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, guard_bytes); |
| EXPECT_TRUE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_TRUE(internal_guard_pages); |
| EXPECT_TRUE(crash_on_fail); |
| |
| const char* kUnusedDisabledName = "driver.sysmem.contiguous_guard_pages_unused_disabled"; |
| fake_parent->SetVariable(kUnusedDisabledName, ""); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, guard_bytes); |
| EXPECT_FALSE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(600), unused_page_check_cycle_period); |
| EXPECT_TRUE(internal_guard_pages); |
| EXPECT_TRUE(crash_on_fail); |
| |
| const char* kUnusedCycleSecondsName = "driver.sysmem.contiguous_guard_pages_unused_cycle_seconds"; |
| fake_parent->SetVariable(kUnusedCycleSecondsName, "42"); |
| EXPECT_EQ(ZX_OK, sysmem.GetContiguousGuardParameters(&guard_bytes, &unused_pages_guarded, |
| &unused_page_check_cycle_period, |
| &internal_guard_pages, &crash_on_fail)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, guard_bytes); |
| EXPECT_FALSE(unused_pages_guarded); |
| EXPECT_EQ(zx::sec(42), unused_page_check_cycle_period); |
| EXPECT_TRUE(internal_guard_pages); |
| EXPECT_TRUE(crash_on_fail); |
| } |
| |
| class FakeDdkSysmem : public zxtest::Test { |
| public: |
| FakeDdkSysmem() {} |
| |
| void SetUp() override { |
| pdev_.SetConfig({ |
| .use_fake_bti = true, |
| }); |
| EXPECT_OK(pdev_loop_.StartThread()); |
| |
| auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| RunSyncOnLoop(pdev_loop_, [this, server = std::move(endpoints.server)]() mutable { |
| outgoing_.emplace(pdev_loop_.dispatcher()); |
| auto device_handler = |
| [this](fidl::ServerEnd<fuchsia_hardware_platform_device::Device> request) { |
| fidl::BindServer(pdev_loop_.dispatcher(), std::move(request), &pdev_); |
| }; |
| fuchsia_hardware_platform_device::Service::InstanceHandler handler( |
| {.device = std::move(device_handler)}); |
| auto service_result = |
| outgoing_->AddService<fuchsia_hardware_platform_device::Service>(std::move(handler)); |
| ZX_ASSERT(service_result.is_ok()); |
| |
| ZX_ASSERT(outgoing_->Serve(std::move(server)).is_ok()); |
| }); |
| |
| fake_parent_->AddFidlService(fuchsia_hardware_platform_device::Service::Name, |
| std::move(endpoints.client)); |
| EXPECT_EQ(sysmem_->Bind(), ZX_OK); |
| } |
| |
| void TearDown() override { |
| ddk::UnbindTxn txn{sysmem_->zxdev()}; |
| sysmem_->DdkUnbind(std::move(txn)); |
| EXPECT_OK(sysmem_->zxdev()->WaitUntilUnbindReplyCalled()); |
| std::ignore = sysmem_.release(); |
| loop_.Shutdown(); |
| RunSyncOnLoop(pdev_loop_, [this] { outgoing_.reset(); }); |
| } |
| |
| fidl::ClientEnd<fuchsia_sysmem::Allocator> Connect() { |
| auto [allocator_client_end, allocator_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::Allocator>::Create(); |
| |
| auto [connector_client_end, connector_server_end] = |
| fidl::Endpoints<fuchsia_hardware_sysmem::DriverConnector>::Create(); |
| |
| fidl::BindServer(loop_.dispatcher(), std::move(connector_server_end), sysmem_.get()); |
| EXPECT_OK(loop_.StartThread()); |
| |
| auto result = fidl::WireCall(connector_client_end)->ConnectV1(std::move(allocator_server_end)); |
| EXPECT_OK(result); |
| |
| return std::move(allocator_client_end); |
| } |
| |
| fidl::ClientEnd<fuchsia_sysmem::BufferCollection> AllocateNonSharedCollection() { |
| fidl::WireSyncClient<fuchsia_sysmem::Allocator> allocator(Connect()); |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| EXPECT_OK(allocator->AllocateNonSharedCollection(std::move(collection_server_end))); |
| return std::move(collection_client_end); |
| } |
| |
| void RunSyncOnLoop(async::Loop& loop, fit::closure to_run) { |
| sync_completion_t done; |
| ZX_ASSERT(ZX_OK == |
| async::PostTask(loop.dispatcher(), [&done, to_run = std::move(to_run)]() mutable { |
| std::move(to_run)(); |
| sync_completion_signal(&done); |
| })); |
| ZX_ASSERT(ZX_OK == sync_completion_wait_deadline(&done, ZX_TIME_INFINITE)); |
| } |
| |
| protected: |
| sysmem_driver::Driver sysmem_ctx_; |
| std::shared_ptr<MockDevice> fake_parent_ = MockDevice::FakeRootParent(); |
| std::unique_ptr<sysmem_driver::Device> sysmem_{new Device{fake_parent_.get(), &sysmem_ctx_}}; |
| |
| fake_pdev::FakePDevFidl pdev_; |
| |
| async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread}; |
| // Separate loop so we can make sync FIDL calls from loop_ to pdev_loop_. |
| async::Loop pdev_loop_{&kAsyncLoopConfigNeverAttachToThread}; |
| // std::optional<> because outgoing_ can only be created, used, deleted on pdev_loop_ |
| std::optional<component::OutgoingDirectory> outgoing_; |
| }; |
| |
| TEST_F(FakeDdkSysmem, TearDownLoop) { |
| // Queue up something that would be processed on the FIDL thread, so we can try to detect a |
| // use-after-free if the FidlServer outlives the sysmem device. |
| AllocateNonSharedCollection(); |
| } |
| |
| // Test that creating and tearing down a SecureMem connection works correctly. |
| TEST_F(FakeDdkSysmem, DummySecureMem) { |
| auto [client, server] = fidl::Endpoints<fuchsia_sysmem::SecureMem>::Create(); |
| ASSERT_OK(sysmem_->CommonSysmemRegisterSecureMem(std::move(client))); |
| |
| // This shouldn't deadlock waiting for a message on the channel. |
| EXPECT_OK(sysmem_->CommonSysmemUnregisterSecureMem()); |
| |
| // This shouldn't cause a panic due to receiving peer closed. |
| client.reset(); |
| } |
| |
| TEST_F(FakeDdkSysmem, NamedToken) { |
| fidl::WireSyncClient<fuchsia_sysmem::Allocator> allocator(Connect()); |
| |
| auto [token_client_end, token_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| EXPECT_OK(allocator->AllocateSharedCollection(std::move(token_server_end))); |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> token(std::move(token_client_end)); |
| |
| // The buffer collection should end up with a name of "a" because that's the highest priority. |
| EXPECT_OK(token->SetName(5u, "c")); |
| EXPECT_OK(token->SetName(100u, "a")); |
| EXPECT_OK(token->SetName(6u, "b")); |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| EXPECT_OK( |
| allocator->BindSharedCollection(token.TakeClientEnd(), std::move(collection_server_end))); |
| |
| // Poll until a matching buffer collection is found. |
| while (true) { |
| bool found_collection = false; |
| sync_completion_t completion; |
| async::PostTask(sysmem_->dispatcher(), [&] { |
| if (sysmem_->logical_buffer_collections().size() == 1) { |
| const auto* logical_collection = *sysmem_->logical_buffer_collections().begin(); |
| auto collection_views = logical_collection->collection_views(); |
| if (collection_views.size() == 1) { |
| auto name = logical_collection->name(); |
| EXPECT_TRUE(name); |
| EXPECT_EQ("a", *name); |
| found_collection = true; |
| } |
| } |
| sync_completion_signal(&completion); |
| }); |
| |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| if (found_collection) |
| break; |
| } |
| } |
| |
| TEST_F(FakeDdkSysmem, NamedClient) { |
| auto collection_client_end = AllocateNonSharedCollection(); |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection( |
| std::move(collection_client_end)); |
| EXPECT_OK(collection->SetDebugClientInfo("a", 5)); |
| |
| // Poll until a matching buffer collection is found. |
| while (true) { |
| bool found_collection = false; |
| sync_completion_t completion; |
| async::PostTask(sysmem_->dispatcher(), [&] { |
| if (sysmem_->logical_buffer_collections().size() == 1) { |
| const auto* logical_collection = *sysmem_->logical_buffer_collections().begin(); |
| if (logical_collection->collection_views().size() == 1) { |
| const BufferCollection* collection = logical_collection->collection_views().front(); |
| if (collection->node_properties().client_debug_info().name == "a") { |
| EXPECT_EQ(5u, collection->node_properties().client_debug_info().id); |
| found_collection = true; |
| } |
| } |
| } |
| sync_completion_signal(&completion); |
| }); |
| |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| if (found_collection) |
| break; |
| } |
| } |
| |
| // Check that the allocator name overrides the collection name. |
| TEST_F(FakeDdkSysmem, NamedAllocatorToken) { |
| fidl::WireSyncClient<fuchsia_sysmem::Allocator> allocator(Connect()); |
| |
| auto [token_client_end, token_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollectionToken>::Create(); |
| |
| EXPECT_OK(allocator->AllocateSharedCollection(std::move(token_server_end))); |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> token(std::move(token_client_end)); |
| |
| EXPECT_OK(token->SetDebugClientInfo("bad", 6)); |
| EXPECT_OK(allocator->SetDebugClientInfo("a", 5)); |
| |
| auto [collection_client_end, collection_server_end] = |
| fidl::Endpoints<fuchsia_sysmem::BufferCollection>::Create(); |
| |
| EXPECT_OK( |
| allocator->BindSharedCollection(token.TakeClientEnd(), std::move(collection_server_end))); |
| |
| // Poll until a matching buffer collection is found. |
| while (true) { |
| bool found_collection = false; |
| sync_completion_t completion; |
| async::PostTask(sysmem_->dispatcher(), [&] { |
| if (sysmem_->logical_buffer_collections().size() == 1) { |
| const auto* logical_collection = *sysmem_->logical_buffer_collections().begin(); |
| auto collection_views = logical_collection->collection_views(); |
| if (collection_views.size() == 1) { |
| const auto& collection = collection_views.front(); |
| if (collection->node_properties().client_debug_info().name == "a") { |
| EXPECT_EQ(5u, collection->node_properties().client_debug_info().id); |
| found_collection = true; |
| } |
| } |
| } |
| sync_completion_signal(&completion); |
| }); |
| |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| if (found_collection) |
| break; |
| } |
| } |
| |
| TEST_F(FakeDdkSysmem, MaxSize) { |
| sysmem_->set_settings(sysmem_driver::Settings{.max_allocation_size = zx_system_get_page_size()}); |
| |
| auto collection_client = AllocateNonSharedCollection(); |
| |
| fuchsia_sysmem::BufferCollectionConstraints constraints; |
| constraints.min_buffer_count() = 1; |
| constraints.has_buffer_memory_constraints() = true; |
| constraints.buffer_memory_constraints().min_size_bytes() = zx_system_get_page_size() * 2; |
| constraints.buffer_memory_constraints().cpu_domain_supported() = true; |
| constraints.usage().cpu() = fuchsia_sysmem::kCpuUsageRead; |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection(std::move(collection_client)); |
| fidl::Arena arena; |
| EXPECT_OK(collection->SetConstraints(true, fidl::ToWire(arena, std::move(constraints)))); |
| |
| // Sysmem should fail the collection and return an error. |
| fidl::WireResult result = collection->WaitForBuffersAllocated(); |
| EXPECT_NE(result.status(), ZX_OK); |
| } |
| |
| // Check that teardown doesn't leak any memory (detected through LSAN). |
| TEST_F(FakeDdkSysmem, TeardownLeak) { |
| auto collection_client = AllocateNonSharedCollection(); |
| |
| fuchsia_sysmem::BufferCollectionConstraints constraints; |
| constraints.min_buffer_count() = 1; |
| constraints.has_buffer_memory_constraints() = true; |
| constraints.buffer_memory_constraints().min_size_bytes() = zx_system_get_page_size(); |
| constraints.buffer_memory_constraints().cpu_domain_supported() = true; |
| constraints.usage().cpu() = fuchsia_sysmem::kCpuUsageRead; |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection(std::move(collection_client)); |
| fidl::Arena arena; |
| EXPECT_OK(collection->SetConstraints(true, fidl::ToWire(arena, std::move(constraints)))); |
| |
| fidl::WireResult result = collection->WaitForBuffersAllocated(); |
| |
| EXPECT_OK(result); |
| EXPECT_OK(result.value().status); |
| |
| for (uint32_t i = 0; i < result.value().buffer_collection_info.buffer_count; i++) { |
| result.value().buffer_collection_info.buffers[i].vmo.reset(); |
| } |
| collection = {}; |
| } |
| |
| // Check that there are no circular references from a VMO to the logical buffer collection. |
| TEST_F(FakeDdkSysmem, BufferLeak) { |
| auto collection_client = AllocateNonSharedCollection(); |
| |
| fuchsia_sysmem::BufferCollectionConstraints constraints; |
| constraints.min_buffer_count() = 1; |
| constraints.has_buffer_memory_constraints() = true; |
| constraints.buffer_memory_constraints().min_size_bytes() = zx_system_get_page_size(); |
| constraints.buffer_memory_constraints().cpu_domain_supported() = true; |
| constraints.usage().cpu() = fuchsia_sysmem::kCpuUsageRead; |
| |
| fidl::WireSyncClient<fuchsia_sysmem::BufferCollection> collection(std::move(collection_client)); |
| fidl::Arena arena; |
| EXPECT_OK(collection->SetConstraints(true, fidl::ToWire(arena, std::move(constraints)))); |
| |
| fidl::WireResult result = collection->WaitForBuffersAllocated(); |
| |
| EXPECT_OK(result); |
| EXPECT_OK(result.value().status); |
| |
| for (uint32_t i = 0; i < result.value().buffer_collection_info.buffer_count; i++) { |
| result.value().buffer_collection_info.buffers[i].vmo.reset(); |
| } |
| |
| collection = {}; |
| |
| // Poll until all buffer collections are deleted. |
| while (true) { |
| bool no_collections = false; |
| sync_completion_t completion; |
| async::PostTask(sysmem_->dispatcher(), [&] { |
| no_collections = sysmem_->logical_buffer_collections().empty(); |
| sync_completion_signal(&completion); |
| }); |
| |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| if (no_collections) |
| break; |
| } |
| } |
| |
| } // namespace |
| } // namespace sysmem_driver |