blob: 326e687564bdb9b025425d9b1ba351524cf2ef58 [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 "device.h"
#include <fuchsia/sysmem/c/fidl.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fake-bti/bti.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/sync/completion.h>
#include <lib/zx/bti.h>
#include <stdlib.h>
#include <zircon/device/sysmem.h>
#include <ddktl/device.h>
#include <ddktl/protocol/platform/bus.h>
#include <ddktl/protocol/platform/device.h>
#include <zxtest/zxtest.h>
#include "../buffer_collection.h"
#include "../device.h"
#include "../driver.h"
#include "../logical_buffer_collection.h"
namespace sysmem_driver {
namespace {
class FakePBus : public ddk::PBusProtocol<FakePBus, ddk::base_protocol> {
public:
FakePBus() : proto_({&pbus_protocol_ops_, this}) {}
const pbus_protocol_t* proto() const { return &proto_; }
zx_status_t PBusDeviceAdd(const pbus_dev_t* dev) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PBusProtocolDeviceAdd(uint32_t proto_id, const pbus_dev_t* dev) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PBusRegisterProtocol(uint32_t proto_id, const void* protocol, size_t protocol_size) {
registered_proto_id_ = proto_id;
return ZX_OK;
}
zx_status_t PBusGetBoardInfo(pdev_board_info_t* out_info) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PBusSetBoardInfo(const pbus_board_info_t* info) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PBusSetBootloaderInfo(const pbus_bootloader_info_t* info) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PBusCompositeDeviceAdd(const pbus_dev_t* dev, const device_fragment_t* fragments_list,
size_t fragments_count, uint32_t coresident_device_index) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PBusRegisterSysSuspendCallback(const pbus_sys_suspend_t* suspend_cbin) {
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t registered_proto_id() const { return registered_proto_id_; }
private:
pbus_protocol_t proto_;
uint32_t registered_proto_id_ = 0;
};
class FakePDev : public ddk::PDevProtocol<FakePDev, ddk::base_protocol> {
public:
FakePDev() : proto_({&pdev_protocol_ops_, this}) {}
const pdev_protocol_t* proto() const { return &proto_; }
zx_status_t PDevGetMmio(uint32_t index, pdev_mmio_t* out_mmio) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PDevGetInterrupt(uint32_t index, uint32_t flags, zx::interrupt* out_irq) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PDevGetBti(uint32_t index, zx::bti* out_bti) {
return fake_bti_create(out_bti->reset_and_get_address());
}
zx_status_t PDevGetSmc(uint32_t index, zx::resource* out_resource) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PDevGetDeviceInfo(pdev_device_info_t* out_info) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PDevGetBoardInfo(pdev_board_info_t* out_info) { return ZX_ERR_NOT_SUPPORTED; }
private:
pdev_protocol_t proto_;
};
TEST(Device, OverrideCommandLine) {
const char* kCommandLine = "test.device.commandline";
setenv(kCommandLine, "5", 1);
uint64_t value = 4096;
Device::OverrideSizeFromCommandLine(kCommandLine, &value);
EXPECT_EQ(65536, value);
setenv(kCommandLine, "65537", 1);
Device::OverrideSizeFromCommandLine(kCommandLine, &value);
EXPECT_EQ(65536 * 2, value);
// Trailing characters should cause the entire value to be ignored.
setenv(kCommandLine, "65536a", 1);
Device::OverrideSizeFromCommandLine(kCommandLine, &value);
EXPECT_EQ(65536 * 2, value);
// Empty values should be ignored.
setenv(kCommandLine, "", 1);
Device::OverrideSizeFromCommandLine(kCommandLine, &value);
EXPECT_EQ(65536 * 2, value);
}
class FakeDdkSysmem : public zxtest::Test {
public:
void SetUp() override {
fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[1], 1);
protocols[0] = {ZX_PROTOCOL_PDEV, *reinterpret_cast<const fake_ddk::Protocol*>(pdev_.proto())};
ddk_.SetProtocols(std::move(protocols));
EXPECT_EQ(sysmem_.Bind(), ZX_OK);
}
void TearDown() override {
sysmem_.DdkAsyncRemove();
EXPECT_OK(ddk_.WaitUntilRemove());
EXPECT_TRUE(ddk_.Ok());
}
zx::channel AllocateNonSharedCollection() {
zx::channel allocator_server, allocator_client;
EXPECT_OK(zx::channel::create(0u, &allocator_server, &allocator_client));
EXPECT_OK(
fuchsia_sysmem_DriverConnectorConnect(ddk_.FidlClient().get(), allocator_server.release()));
zx::channel collection_server, collection_client;
EXPECT_OK(zx::channel::create(0u, &collection_server, &collection_client));
// 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.
EXPECT_OK(fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator_client.get(),
collection_server.release()));
return collection_client;
}
protected:
sysmem_driver::Driver sysmem_ctx_;
sysmem_driver::Device sysmem_{fake_ddk::kFakeParent, &sysmem_ctx_};
FakePDev pdev_;
// ddk must be destroyed before sysmem because it may be executing messages against sysmem on
// another thread.
fake_ddk::Bind ddk_;
};
class FakeDdkSysmemPbus : public FakeDdkSysmem {
public:
void SetUp() override {
fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[2], 2);
protocols[0] = {ZX_PROTOCOL_PBUS, *reinterpret_cast<const fake_ddk::Protocol*>(pbus_.proto())};
protocols[1] = {ZX_PROTOCOL_PDEV, *reinterpret_cast<const fake_ddk::Protocol*>(pdev_.proto())};
ddk_.SetProtocols(std::move(protocols));
EXPECT_EQ(sysmem_.Bind(), ZX_OK);
}
protected:
FakePBus pbus_;
};
TEST_F(FakeDdkSysmem, TearDownLoop) {
zx::channel allocator_server, allocator_client;
ASSERT_OK(zx::channel::create(0u, &allocator_server, &allocator_client));
EXPECT_OK(
fuchsia_sysmem_DriverConnectorConnect(ddk_.FidlClient().get(), allocator_server.release()));
zx::channel collection_server, collection_client;
ASSERT_OK(zx::channel::create(0u, &collection_server, &collection_client));
// 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.
EXPECT_OK(fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator_client.get(),
collection_server.release()));
}
// Test that creating and tearing down a SecureMem connection works correctly.
TEST_F(FakeDdkSysmem, DummySecureMem) {
zx::channel securemem_server, securemem_client;
ASSERT_OK(zx::channel::create(0u, &securemem_server, &securemem_client));
EXPECT_EQ(ZX_OK, sysmem_.SysmemRegisterSecureMem(std::move(securemem_server)));
// This shouldn't deadlock waiting for a message on the channel.
EXPECT_EQ(ZX_OK, sysmem_.SysmemUnregisterSecureMem());
// This shouldn't cause a panic due to receiving peer closed.
securemem_client.reset();
}
TEST_F(FakeDdkSysmem, NamedToken) {
zx::channel allocator_server, allocator_client;
ASSERT_OK(zx::channel::create(0u, &allocator_server, &allocator_client));
EXPECT_OK(
fuchsia_sysmem_DriverConnectorConnect(ddk_.FidlClient().get(), allocator_server.release()));
zx::channel token_server, token_client;
ASSERT_OK(zx::channel::create(0u, &token_server, &token_client));
// 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.
EXPECT_OK(fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release()));
// The buffer collection should end up with a name of "a" because that's the highest priority.
EXPECT_OK(fuchsia_sysmem_BufferCollectionTokenSetName(token_client.get(), 5u, "c", 1));
EXPECT_OK(fuchsia_sysmem_BufferCollectionTokenSetName(token_client.get(), 100u, "a", 1));
EXPECT_OK(fuchsia_sysmem_BufferCollectionTokenSetName(token_client.get(), 6u, "b", 1));
zx::channel collection_server, collection_client;
ASSERT_OK(zx::channel::create(0u, &collection_server, &collection_client));
EXPECT_OK(fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release()));
// 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) {
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) {
zx::channel allocator_server, allocator_client;
ASSERT_OK(zx::channel::create(0u, &allocator_server, &allocator_client));
EXPECT_OK(
fuchsia_sysmem_DriverConnectorConnect(ddk_.FidlClient().get(), allocator_server.release()));
zx::channel collection_server, collection_client;
ASSERT_OK(zx::channel::create(0u, &collection_server, &collection_client));
// 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.
EXPECT_OK(fuchsia_sysmem_AllocatorAllocateNonSharedCollection(allocator_client.get(),
collection_server.release()));
EXPECT_OK(fuchsia_sysmem_BufferCollectionSetDebugClientInfo(collection_client.get(), "a", 1, 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 auto& collection = logical_collection->collection_views().begin()->second;
if (collection->debug_name() == "a") {
EXPECT_EQ(5u, collection->debug_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) {
zx::channel allocator_server, allocator_client;
ASSERT_OK(zx::channel::create(0u, &allocator_server, &allocator_client));
EXPECT_OK(
fuchsia_sysmem_DriverConnectorConnect(ddk_.FidlClient().get(), allocator_server.release()));
zx::channel token_server, token_client;
ASSERT_OK(zx::channel::create(0u, &token_server, &token_client));
// 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.
EXPECT_OK(fuchsia_sysmem_AllocatorAllocateSharedCollection(allocator_client.get(),
token_server.release()));
EXPECT_OK(
fuchsia_sysmem_BufferCollectionTokenSetDebugClientInfo(token_client.get(), "bad", 3, 6));
EXPECT_OK(fuchsia_sysmem_AllocatorSetDebugClientInfo(allocator_client.get(), "a", 1, 5));
zx::channel collection_server, collection_client;
ASSERT_OK(zx::channel::create(0u, &collection_server, &collection_client));
EXPECT_OK(fuchsia_sysmem_AllocatorBindSharedCollection(
allocator_client.get(), token_client.release(), collection_server.release()));
// 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 auto& collection = logical_collection->collection_views().begin()->second;
if (collection->debug_name() == "a") {
EXPECT_EQ(5u, collection->debug_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 = PAGE_SIZE});
zx::channel 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 = PAGE_SIZE * 2;
constraints.buffer_memory_constraints.cpu_domain_supported = true;
constraints.usage.cpu = fuchsia_sysmem_cpuUsageRead;
EXPECT_OK(
fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true, &constraints));
fuchsia_sysmem_BufferCollectionInfo_2 info;
zx_status_t status;
// Sysmem should fail the collection and return an error.
EXPECT_NE(ZX_OK, fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(collection_client.get(),
&status, &info));
}
// Check that teardown doesn't leak any memory (detected through LSAN).
TEST_F(FakeDdkSysmem, TeardownLeak) {
zx::channel 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 = PAGE_SIZE;
constraints.buffer_memory_constraints.cpu_domain_supported = true;
constraints.usage.cpu = fuchsia_sysmem_cpuUsageRead;
EXPECT_OK(
fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true, &constraints));
fuchsia_sysmem_BufferCollectionInfo_2 info;
zx_status_t status;
EXPECT_OK(fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(collection_client.get(), &status,
&info));
EXPECT_OK(status);
for (uint32_t i = 0; i < info.buffer_count; i++) {
zx_handle_close(info.buffers[i].vmo);
}
collection_client.reset();
}
TEST_F(FakeDdkSysmemPbus, Register) { EXPECT_EQ(ZX_PROTOCOL_SYSMEM, pbus_.registered_proto_id()); }
} // namespace
} // namespace sysmem_driver