| // Copyright 2021 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 "src/devices/misc/drivers/compat/driver.h" |
| |
| #include <dirent.h> |
| #include <fidl/fuchsia.boot/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.device.fs/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.driver.framework/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.io/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.logger/cpp/wire.h> |
| #include <fidl/fuchsia.scheduler/cpp/wire_test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async-testing/test_loop.h> |
| #include <lib/fdf/testing.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fit/defer.h> |
| #include <lib/sync/cpp/completion.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| #include <mock-boot-arguments/server.h> |
| |
| #include "sdk/lib/driver_runtime/testing/loop_fixture/test_loop_fixture.h" |
| #include "src/devices/lib/compat/symbols.h" |
| #include "src/devices/misc/drivers/compat/v1_test.h" |
| #include "src/lib/storage/vfs/cpp/managed_vfs.h" |
| #include "src/lib/storage/vfs/cpp/pseudo_dir.h" |
| #include "src/lib/storage/vfs/cpp/service.h" |
| |
| namespace fboot = fuchsia_boot; |
| namespace fdata = fuchsia_data; |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } |
| namespace fio = fuchsia_io; |
| namespace flogger = fuchsia_logger; |
| namespace frunner = fuchsia_component_runner; |
| |
| constexpr auto kOpenFlags = fio::wire::OpenFlags::kRightReadable | |
| fio::wire::OpenFlags::kRightExecutable | |
| fio::wire::OpenFlags::kNotDirectory; |
| constexpr auto kVmoFlags = fio::wire::VmoFlags::kRead | fio::wire::VmoFlags::kExecute; |
| |
| namespace { |
| |
| zx::vmo GetVmo(std::string_view path) { |
| zx::status endpoints = fidl::CreateEndpoints<fio::File>(); |
| EXPECT_TRUE(endpoints.is_ok()) << endpoints.status_string(); |
| zx_status_t status = fdio_open(path.data(), static_cast<uint32_t>(kOpenFlags), |
| endpoints->server.channel().release()); |
| EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status); |
| fidl::WireResult result = fidl::WireCall(endpoints->client)->GetBackingMemory(kVmoFlags); |
| EXPECT_TRUE(result.ok()) << result.FormatDescription(); |
| const auto& response = result.value(); |
| EXPECT_TRUE(response.is_ok()) << zx_status_get_string(response.error_value()); |
| return std::move(response.value()->vmo); |
| } |
| |
| class TestNode : public fidl::testing::WireTestBase<fdf::Node> { |
| public: |
| bool HasChildren() const { return !controllers_.empty() || !nodes_.empty(); } |
| |
| private: |
| void AddChild(AddChildRequestView request, AddChildCompleter::Sync& completer) override { |
| controllers_.push_back(std::move(request->controller)); |
| nodes_.push_back(std::move(request->node)); |
| completer.ReplySuccess(); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: Node::%s\n", name.data()); |
| } |
| |
| std::vector<fidl::ServerEnd<fdf::NodeController>> controllers_; |
| std::vector<fidl::ServerEnd<fdf::Node>> nodes_; |
| }; |
| |
| class TestRootResource : public fidl::testing::WireTestBase<fboot::RootResource> { |
| public: |
| TestRootResource() { EXPECT_EQ(ZX_OK, zx::event::create(0, &fake_resource_)); } |
| |
| private: |
| void Get(GetCompleter::Sync& completer) override { |
| zx::event duplicate; |
| ASSERT_EQ(ZX_OK, fake_resource_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate)); |
| completer.Reply(zx::resource(duplicate.release())); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: RootResource::%s\n", name.data()); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // An event is similar enough that we can pretend it's the root resource, in that we can |
| // send it over a FIDL channel. |
| zx::event fake_resource_; |
| }; |
| |
| class TestItems : public fidl::testing::WireTestBase<fboot::Items> { |
| private: |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: Items::%s\n", name.data()); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| }; |
| |
| class TestFile : public fidl::testing::WireTestBase<fio::File> { |
| public: |
| void SetStatus(zx_status_t status) { status_ = status; } |
| void SetVmo(zx::vmo vmo) { vmo_ = std::move(vmo); } |
| |
| private: |
| void GetBackingMemory(GetBackingMemoryRequestView request, |
| GetBackingMemoryCompleter::Sync& completer) override { |
| if (status_ != ZX_OK) { |
| completer.ReplyError(status_); |
| } else { |
| completer.ReplySuccess(std::move(vmo_)); |
| } |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: File::%s\n", name.data()); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| zx_status_t status_ = ZX_OK; |
| zx::vmo vmo_; |
| }; |
| |
| class TestDirectory : public fidl::testing::WireTestBase<fio::Directory> { |
| public: |
| using OpenHandler = fit::function<void(OpenRequestView)>; |
| |
| void SetOpenHandler(OpenHandler open_handler) { open_handler_ = std::move(open_handler); } |
| |
| private: |
| void Open(OpenRequestView request, OpenCompleter::Sync& completer) override { |
| open_handler_(std::move(request)); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: Directory::%s\n", name.data()); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| OpenHandler open_handler_; |
| }; |
| |
| class TestDevice : public fidl::WireServer<fuchsia_driver_compat::Device> { |
| void GetTopologicalPath(GetTopologicalPathCompleter::Sync& completer) override { |
| completer.Reply("/dev/test/my-device"); |
| } |
| |
| void GetMetadata(GetMetadataCompleter::Sync& completer) override { |
| std::vector<fuchsia_driver_compat::wire::Metadata> metadata; |
| |
| std::vector<uint8_t> bytes_1 = {1, 2, 3}; |
| zx::vmo vmo_1; |
| ASSERT_EQ(ZX_OK, zx::vmo::create(bytes_1.size(), 0, &vmo_1)); |
| vmo_1.write(bytes_1.data(), 0, bytes_1.size()); |
| size_t size = bytes_1.size(); |
| ASSERT_EQ(ZX_OK, vmo_1.set_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size))); |
| metadata.push_back(fuchsia_driver_compat::wire::Metadata{.type = 1, .data = std::move(vmo_1)}); |
| |
| std::vector<uint8_t> bytes_2 = {4, 5, 6}; |
| zx::vmo vmo_2; |
| ASSERT_EQ(ZX_OK, zx::vmo::create(bytes_1.size(), 0, &vmo_2)); |
| vmo_2.write(bytes_2.data(), 0, bytes_2.size()); |
| ASSERT_EQ(ZX_OK, vmo_2.set_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size))); |
| metadata.push_back(fuchsia_driver_compat::wire::Metadata{.type = 2, .data = std::move(vmo_2)}); |
| |
| completer.ReplySuccess(fidl::VectorView<fuchsia_driver_compat::wire::Metadata>::FromExternal( |
| metadata.data(), metadata.size())); |
| } |
| |
| void ConnectFidl(ConnectFidlRequestView request, ConnectFidlCompleter::Sync& completer) override { |
| } |
| }; |
| |
| class TestProfileProvider : public fidl::testing::WireTestBase<fuchsia_scheduler::ProfileProvider> { |
| public: |
| void GetProfile(GetProfileRequestView request, GetProfileCompleter::Sync& completer) override { |
| if (get_profile_callback_) { |
| get_profile_callback_(request->priority, |
| std::string_view(request->name.data(), request->name.size())); |
| } |
| completer.Reply(ZX_OK, zx::profile()); |
| } |
| void SetGetProfileCallback(std::function<void(uint32_t, std::string_view)> cb) { |
| get_profile_callback_ = std::move(cb); |
| } |
| |
| void GetDeadlineProfile(GetDeadlineProfileRequestView request, |
| GetDeadlineProfileCompleter::Sync& completer) override { |
| if (get_deadline_profile_callback_) { |
| get_deadline_profile_callback_(request); |
| } |
| completer.Reply(ZX_OK, zx::profile()); |
| } |
| void SetGetDeadlineProfileCallback(std::function<void(GetDeadlineProfileRequestView&)> cb) { |
| get_deadline_profile_callback_ = std::move(cb); |
| } |
| |
| void SetProfileByRole(SetProfileByRoleRequestView request, |
| SetProfileByRoleCompleter::Sync& completer) override { |
| if (set_profile_by_role_callback_) { |
| set_profile_by_role_callback_(request); |
| } |
| completer.Reply(ZX_OK); |
| } |
| void SetSetProfileByRoleCallback(std::function<void(SetProfileByRoleRequestView&)> cb) { |
| set_profile_by_role_callback_ = std::move(cb); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: ProfileProvider::%s", name.data()); |
| } |
| |
| private: |
| std::function<void(uint32_t, std::string_view)> get_profile_callback_; |
| std::function<void(GetDeadlineProfileRequestView&)> get_deadline_profile_callback_; |
| std::function<void(SetProfileByRoleRequestView&)> set_profile_by_role_callback_; |
| }; |
| |
| class TestExporter : public fidl::testing::WireTestBase<fuchsia_device_fs::Exporter> { |
| public: |
| void Export(ExportRequestView request, ExportCompleter::Sync& completer) override { |
| completer.ReplySuccess(); |
| } |
| |
| void ExportOptions(ExportOptionsRequestView request, |
| ExportOptionsCompleter::Sync& completer) override { |
| completer.ReplySuccess(); |
| } |
| |
| void MakeVisible(MakeVisibleRequestView request, MakeVisibleCompleter::Sync& completer) override { |
| completer.ReplySuccess(); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: TestExporter::%s", name.data()); |
| } |
| }; |
| |
| } // namespace |
| |
| class DriverTest : public gtest::DriverTestLoopFixture { |
| protected: |
| TestNode& node() { return node_; } |
| TestFile& compat_file() { return compat_file_; } |
| |
| void SetUp() override { |
| DriverTestLoopFixture::SetUp(); |
| fidl_loop_.StartThread("fidl-server-thread"); |
| |
| std::map<std::string, std::string> arguments; |
| arguments["kernel.shell"] = "true"; |
| arguments["driver.foo"] = "true"; |
| arguments["clock.backstop"] = "0"; |
| boot_args_ = mock_boot_arguments::Server(std::move(arguments)); |
| } |
| |
| void TearDown() override { |
| DriverTestLoopFixture::TearDown(); |
| |
| vfs_->Shutdown([](auto status) {}); |
| RunUntilDispatchersIdle(); |
| } |
| |
| std::unique_ptr<compat::Driver> StartDriver(std::string_view v1_driver_path, |
| const zx_protocol_device_t* ops) { |
| auto node_endpoints = fidl::CreateEndpoints<fdf::Node>(); |
| EXPECT_TRUE(node_endpoints.is_ok()); |
| auto outgoing_dir_endpoints = fidl::CreateEndpoints<fio::Directory>(); |
| EXPECT_TRUE(outgoing_dir_endpoints.is_ok()); |
| auto pkg_endpoints = fidl::CreateEndpoints<fio::Directory>(); |
| EXPECT_TRUE(pkg_endpoints.is_ok()); |
| auto svc_endpoints = fidl::CreateEndpoints<fio::Directory>(); |
| EXPECT_TRUE(svc_endpoints.is_ok()); |
| auto compat_service_endpoints = fidl::CreateEndpoints<fio::Directory>(); |
| EXPECT_TRUE(compat_service_endpoints.is_ok()); |
| |
| // Setup the node. |
| fidl::BindServer(dispatcher(), std::move(node_endpoints->server), &node_); |
| |
| // Setup and bind "/pkg" directory. |
| compat_file_.SetVmo(GetVmo("/pkg/driver/compat.so")); |
| v1_test_file_.SetVmo(GetVmo(v1_driver_path)); |
| firmware_file_.SetVmo(GetVmo("/pkg/lib/firmware/test")); |
| pkg_directory_.SetOpenHandler([this](TestDirectory::OpenRequestView request) { |
| fidl::ServerEnd<fio::File> server_end(request->object.TakeChannel()); |
| if (request->path.get() == "driver/compat.so") { |
| fidl::BindServer(dispatcher(), std::move(server_end), &compat_file_); |
| } else if (request->path.get() == "driver/v1_test.so") { |
| fidl::BindServer(dispatcher(), std::move(server_end), &v1_test_file_); |
| } else if (request->path.get() == "lib/firmware/test") { |
| fidl::BindServer(dispatcher(), std::move(server_end), &firmware_file_); |
| } else { |
| FAIL() << "Unexpected file: " << request->path.get(); |
| } |
| }); |
| fidl::BindServer(dispatcher(), std::move(pkg_endpoints->server), &pkg_directory_); |
| |
| // Setup and bind "/svc" directory. |
| { |
| auto svc = fbl::MakeRefCounted<fs::PseudoDir>(); |
| svc->AddEntry(fidl::DiscoverableProtocolName<flogger::LogSink>, |
| fbl::MakeRefCounted<fs::Service>([](zx::channel server) { |
| return fdio_service_connect_by_name( |
| fidl::DiscoverableProtocolName<flogger::LogSink>, server.release()); |
| })); |
| |
| svc->AddEntry(fidl::DiscoverableProtocolName<fboot::RootResource>, |
| fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fboot::RootResource> server_end(std::move(server)); |
| fidl::BindServer(dispatcher(), std::move(server_end), &root_resource_); |
| return ZX_OK; |
| })); |
| |
| svc->AddEntry(fidl::DiscoverableProtocolName<fboot::Items>, |
| fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fboot::Items> server_end(std::move(server)); |
| fidl::BindServer(dispatcher(), std::move(server_end), &items_); |
| return ZX_OK; |
| })); |
| |
| svc->AddEntry(fidl::DiscoverableProtocolName<fboot::Arguments>, |
| fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fboot::Arguments> server_end(std::move(server)); |
| fidl::BindServer(dispatcher(), std::move(server_end), &boot_args_); |
| return ZX_OK; |
| })); |
| |
| svc->AddEntry(fidl::DiscoverableProtocolName<fuchsia_device_fs::Exporter>, |
| fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fuchsia_device_fs::Exporter> server_end(std::move(server)); |
| fidl::BindServer(fidl_loop_.dispatcher(), std::move(server_end), &exporter_); |
| return ZX_OK; |
| })); |
| |
| svc->AddEntry( |
| fidl::DiscoverableProtocolName<fuchsia_scheduler::ProfileProvider>, |
| fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fuchsia_scheduler::ProfileProvider> server_end(std::move(server)); |
| fidl::BindServer(dispatcher(), std::move(server_end), &profile_provider_); |
| return ZX_OK; |
| })); |
| |
| auto compat_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| auto compat_default_dir = fbl::MakeRefCounted<fs::PseudoDir>(); |
| compat_default_dir->AddEntry( |
| "device", fbl::MakeRefCounted<fs::Service>([this](zx::channel server) { |
| fidl::ServerEnd<fuchsia_driver_compat::Device> server_end(std::move(server)); |
| fidl::BindServer(dispatcher(), std::move(server_end), &test_device_); |
| return ZX_OK; |
| })); |
| compat_dir->AddEntry("default", compat_default_dir); |
| svc->AddEntry("fuchsia.driver.compat.Service", compat_dir); |
| |
| vfs_.emplace(dispatcher()); |
| vfs_->ServeDirectory(svc, std::move(svc_endpoints->server)); |
| } |
| |
| auto entry_pkg = frunner::ComponentNamespaceEntry( |
| {.path = std::string("/pkg"), .directory = std::move(pkg_endpoints->client)}); |
| auto entry_svc = frunner::ComponentNamespaceEntry( |
| {.path = std::string("/svc"), .directory = std::move(svc_endpoints->client)}); |
| std::vector<frunner::ComponentNamespaceEntry> ns_entries; |
| ns_entries.push_back(std::move(entry_pkg)); |
| ns_entries.push_back(std::move(entry_svc)); |
| |
| std::vector<fdf::NodeSymbol> symbols( |
| {fdf::NodeSymbol({.name = compat::kOps, .address = reinterpret_cast<uint64_t>(ops)})}); |
| |
| auto program_entry = |
| fdata::DictionaryEntry("compat", std::make_unique<fdata::DictionaryValue>( |
| fdata::DictionaryValue::WithStr("driver/v1_test.so"))); |
| std::vector<fdata::DictionaryEntry> program_vec; |
| program_vec.push_back(std::move(program_entry)); |
| fdata::Dictionary program({.entries = std::move(program_vec)}); |
| |
| driver::DriverStartArgs start_args( |
| {.node = std::move(node_endpoints->client), |
| .symbols = std::move(symbols), |
| .url = std::string("fuchsia-pkg://fuchsia.com/driver#meta/driver.cm"), |
| .program = std::move(program), |
| .ns = std::move(ns_entries), |
| .outgoing_dir = std::move(outgoing_dir_endpoints->server), |
| .config = std::nullopt}); |
| |
| // Start driver. |
| auto result = |
| compat::DriverFactory::CreateDriver(std::move(start_args), driver_dispatcher().borrow()); |
| EXPECT_EQ(ZX_OK, result.status_value()); |
| auto* driver = result.value().release(); |
| auto* casted = static_cast<compat::Driver*>(driver); |
| return std::unique_ptr<compat::Driver>(casted); |
| } |
| |
| void RunUntilDispatchersIdle() { |
| bool ran = false; |
| do { |
| WaitUntilIdle(); |
| ran = RunTestLoopUntilIdle(); |
| } while (ran); |
| } |
| |
| bool RunTestLoopUntilIdle() { return test_loop_.RunUntilIdle(); } |
| |
| void WaitForChildDeviceAdded() { |
| while (!node().HasChildren()) { |
| RunUntilDispatchersIdle(); |
| } |
| EXPECT_TRUE(node().HasChildren()); |
| } |
| |
| async_dispatcher_t* dispatcher() { return test_loop_.dispatcher(); } |
| |
| TestProfileProvider profile_provider_; |
| |
| private: |
| TestNode node_; |
| TestRootResource root_resource_; |
| mock_boot_arguments::Server boot_args_; |
| TestItems items_; |
| TestDevice test_device_; |
| TestFile compat_file_; |
| TestFile v1_test_file_; |
| TestFile firmware_file_; |
| TestDirectory pkg_directory_; |
| TestExporter exporter_; |
| std::optional<fs::ManagedVfs> vfs_; |
| |
| // This loop is for FIDL servers that get called in a sync fashion from |
| // the driver. |
| async::Loop fidl_loop_ = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| async::TestLoop test_loop_; |
| }; |
| |
| TEST_F(DriverTest, Start) { |
| zx_protocol_device_t ops{ |
| .get_protocol = [](void*, uint32_t, void*) { return ZX_OK; }, |
| }; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // Verify v1_test.so state after bind. |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_TRUE(v1_test->did_bind); |
| EXPECT_EQ(ZX_OK, v1_test->status); |
| EXPECT_FALSE(v1_test->did_create); |
| EXPECT_FALSE(v1_test->did_release); |
| } |
| |
| // Verify v1_test.so state after release. |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_TRUE(v1_test->did_release); |
| } |
| } |
| |
| TEST_F(DriverTest, Start_WithCreate) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_create_test.so", &ops); |
| |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // Verify v1_test.so state after bind. |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_EQ(ZX_OK, v1_test->status); |
| EXPECT_FALSE(v1_test->did_bind); |
| EXPECT_TRUE(v1_test->did_create); |
| EXPECT_FALSE(v1_test->did_release); |
| } |
| |
| // Verify v1_test.so state after release. |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_TRUE(v1_test->did_release); |
| } |
| } |
| |
| TEST_F(DriverTest, Start_MissingBindAndCreate) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_missing_test.so", &ops); |
| |
| // Verify that v1_test.so has not added a child device. |
| RunUntilDispatchersIdle(); |
| EXPECT_FALSE(node().HasChildren()); |
| |
| // Verify that v1_test.so has not set a context. |
| EXPECT_EQ(nullptr, driver->Context()); |
| |
| ShutdownDriverDispatcher(); |
| } |
| |
| TEST_F(DriverTest, Start_DeviceAddNull) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_device_add_null_test.so", &ops); |
| |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| ShutdownDriverDispatcher(); |
| } |
| |
| TEST_F(DriverTest, Start_CheckCompatService) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_device_add_null_test.so", &ops); |
| |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Check topological path. |
| ASSERT_STREQ(driver->GetDevice().topological_path().data(), "/dev/test/my-device"); |
| |
| // Check metadata. |
| std::array<uint8_t, 3> expected_metadata; |
| std::array<uint8_t, 3> metadata; |
| size_t size = 0; |
| |
| ASSERT_EQ(driver->GetDevice().GetMetadata(1, metadata.data(), metadata.size(), &size), ZX_OK); |
| ASSERT_EQ(size, 3ul); |
| expected_metadata = {1, 2, 3}; |
| ASSERT_EQ(metadata, expected_metadata); |
| |
| ASSERT_EQ(driver->GetDevice().GetMetadata(2, metadata.data(), metadata.size(), &size), ZX_OK); |
| ASSERT_EQ(size, 3ul); |
| expected_metadata = {4, 5, 6}; |
| ASSERT_EQ(metadata, expected_metadata); |
| |
| ShutdownDriverDispatcher(); |
| } |
| |
| TEST_F(DriverTest, DISABLED_Start_RootResourceIsConstant) { |
| // Set the root resource before the test starts. |
| zx_handle_t resource; |
| { |
| std::scoped_lock lock(kDriverGlobalsLock); |
| ASSERT_EQ(ZX_OK, zx_event_create(0, kRootResource.reset_and_get_address())); |
| resource = kRootResource.get(); |
| } |
| |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_device_add_null_test.so", &ops); |
| |
| RunUntilDispatchersIdle(); |
| |
| zx_handle_t resource2 = get_root_resource(); |
| |
| // Check that the root resource's value did not change. |
| ASSERT_EQ(resource, resource2); |
| |
| ShutdownDriverDispatcher(); |
| } |
| |
| TEST_F(DriverTest, Start_GetBackingMemory) { |
| compat_file().SetStatus(ZX_ERR_UNAVAILABLE); |
| |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| |
| // Verify that v1_test.so has not added a child device. |
| RunUntilDispatchersIdle(); |
| EXPECT_FALSE(node().HasChildren()); |
| |
| // Verify that v1_test.so has not set a context. |
| EXPECT_EQ(nullptr, driver->Context()); |
| |
| ShutdownDriverDispatcher(); |
| } |
| |
| TEST_F(DriverTest, Start_BindFailed) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| |
| // Verify that v1_test.so has set a context. |
| while (!driver->Context()) { |
| RunUntilDispatchersIdle(); |
| } |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // Verify that v1_test.so has been bound. |
| while (!v1_test->did_bind) { |
| RunUntilDispatchersIdle(); |
| } |
| |
| // Verify that v1_test.so has not added a child device. |
| EXPECT_FALSE(node().HasChildren()); |
| |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_TRUE(v1_test->did_bind); |
| EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, v1_test->status); |
| |
| EXPECT_FALSE(v1_test->did_create); |
| EXPECT_FALSE(v1_test->did_release); |
| } |
| |
| // Verify v1_test.so state after release. |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| |
| { |
| const std::lock_guard<std::mutex> lock(v1_test->lock); |
| EXPECT_TRUE(v1_test->did_release); |
| } |
| } |
| |
| TEST_F(DriverTest, LoadFirwmareAsync) { |
| zx_protocol_device_t ops{}; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| |
| // Verify that v1_test.so has set a context. |
| while (!driver->Context()) { |
| RunUntilDispatchersIdle(); |
| } |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // Verify that v1_test.so has not added a child device. |
| EXPECT_FALSE(node().HasChildren()); |
| |
| bool was_called = false; |
| |
| driver->LoadFirmwareAsync( |
| nullptr, "test", |
| [](void* ctx, zx_status_t status, zx_handle_t fw, size_t size) { |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_EQ(size, 16ul); |
| zx::vmo vmo(fw); |
| auto buf = std::vector<char>(size); |
| vmo.read(buf.data(), 0, size); |
| buf.push_back('\0'); |
| ASSERT_STREQ(buf.data(), "Hello, firmware!"); |
| |
| *reinterpret_cast<bool*>(ctx) = true; |
| }, |
| &was_called); |
| while (!was_called) { |
| RunUntilDispatchersIdle(); |
| } |
| ASSERT_TRUE(was_called); |
| |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| } |
| |
| TEST_F(DriverTest, GetProfile) { |
| profile_provider_.SetGetProfileCallback([](uint32_t priority, std::string_view name) { |
| ASSERT_EQ(10u, priority); |
| ASSERT_EQ("test-profile", name); |
| }); |
| |
| zx_protocol_device_t ops{ |
| .get_protocol = [](void*, uint32_t, void*) { return ZX_OK; }, |
| }; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // device_get_profile blocks, so we have to do it in a separate thread. |
| sync_completion_t finished; |
| auto thread = std::thread([&finished, &v1_test]() { |
| zx_handle_t out_profile; |
| ASSERT_EQ(ZX_OK, device_get_profile(v1_test->zxdev, 10, "test-profile", &out_profile)); |
| sync_completion_signal(&finished); |
| }); |
| do { |
| RunUntilDispatchersIdle(); |
| } while (sync_completion_wait(&finished, ZX_TIME_INFINITE_PAST) == ZX_ERR_TIMED_OUT); |
| thread.join(); |
| |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| } |
| |
| TEST_F(DriverTest, GetDeadlineProfile) { |
| profile_provider_.SetGetDeadlineProfileCallback( |
| [](TestProfileProvider::GetDeadlineProfileRequestView& rv) { |
| ASSERT_EQ(10u, rv->capacity); |
| ASSERT_EQ(20u, rv->deadline); |
| ASSERT_EQ(30u, rv->period); |
| std::string_view sv(rv->name.data(), rv->name.size()); |
| ASSERT_EQ("test-profile", sv); |
| }); |
| |
| zx_protocol_device_t ops{ |
| .get_protocol = [](void*, uint32_t, void*) { return ZX_OK; }, |
| }; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // device_get_profile blocks, so we have to do it in a separate thread. |
| sync_completion_t finished; |
| auto thread = std::thread([&finished, &v1_test]() { |
| zx_handle_t out_profile; |
| ASSERT_EQ(ZX_OK, device_get_deadline_profile(v1_test->zxdev, 10, 20, 30, "test-profile", |
| &out_profile)); |
| sync_completion_signal(&finished); |
| }); |
| do { |
| RunUntilDispatchersIdle(); |
| } while (sync_completion_wait(&finished, ZX_TIME_INFINITE_PAST) == ZX_ERR_TIMED_OUT); |
| thread.join(); |
| |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| } |
| |
| TEST_F(DriverTest, GetVariable) { |
| zx_protocol_device_t ops{ |
| .get_protocol = [](void*, uint32_t, void*) { return ZX_OK; }, |
| }; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // device_get_profile blocks, so we have to do it in a separate thread. |
| sync_completion_t finished; |
| auto thread = std::thread([&finished, &v1_test]() { |
| char variable[20]; |
| size_t actual; |
| ASSERT_EQ(ZX_OK, device_get_variable(v1_test->zxdev, "driver.foo", variable, sizeof(variable), |
| &actual)); |
| ASSERT_EQ(actual, 4u); |
| ASSERT_EQ(strncmp(variable, "true", sizeof(variable)), 0); |
| ASSERT_EQ(ZX_OK, device_get_variable(v1_test->zxdev, "clock.backstop", variable, |
| sizeof(variable), &actual)); |
| ASSERT_EQ(actual, 1u); |
| ASSERT_EQ(strncmp(variable, "0", sizeof(variable)), 0); |
| // Invalid variable name |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, device_get_variable(v1_test->zxdev, "kernel.shell", variable, |
| sizeof(variable), &actual)); |
| // Buffer too small |
| ASSERT_EQ(ZX_ERR_BUFFER_TOO_SMALL, |
| device_get_variable(v1_test->zxdev, "driver.foo", variable, 1, &actual)); |
| ASSERT_EQ(actual, 4u); |
| sync_completion_signal(&finished); |
| }); |
| do { |
| RunUntilDispatchersIdle(); |
| } while (sync_completion_wait(&finished, ZX_TIME_INFINITE_PAST) == ZX_ERR_TIMED_OUT); |
| thread.join(); |
| |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| } |
| |
| TEST_F(DriverTest, SetProfileByRole) { |
| profile_provider_.SetSetProfileByRoleCallback( |
| [](TestProfileProvider::SetProfileByRoleRequestView& rv) { |
| ASSERT_TRUE(rv->thread.is_valid()); |
| ASSERT_EQ("test-profile", rv->role.get()); |
| }); |
| |
| zx_protocol_device_t ops{ |
| .get_protocol = [](void*, uint32_t, void*) { return ZX_OK; }, |
| }; |
| auto driver = StartDriver("/pkg/driver/v1_test.so", &ops); |
| // Verify that v1_test.so has added a child device. |
| WaitForChildDeviceAdded(); |
| |
| // Verify that v1_test.so has set a context. |
| std::unique_ptr<V1Test> v1_test(static_cast<V1Test*>(driver->Context())); |
| ASSERT_NE(nullptr, v1_test.get()); |
| |
| // device_get_profile blocks, so we have to do it in a separate thread. |
| sync_completion_t finished; |
| auto thread = std::thread([&finished, &v1_test]() { |
| constexpr char kThreadName[] = "test-thread"; |
| zx::thread thread; |
| ASSERT_EQ(ZX_OK, zx::thread::create(*zx::process::self(), kThreadName, sizeof(kThreadName), 0, |
| &thread)); |
| ASSERT_EQ(ZX_OK, device_set_profile_by_role(v1_test->zxdev, thread.release(), "test-profile", |
| strlen("test-profile"))); |
| sync_completion_signal(&finished); |
| }); |
| do { |
| RunUntilDispatchersIdle(); |
| } while (sync_completion_wait(&finished, ZX_TIME_INFINITE_PAST) == ZX_ERR_TIMED_OUT); |
| thread.join(); |
| |
| ShutdownDriverDispatcher(); |
| driver.reset(); |
| ASSERT_TRUE(RunTestLoopUntilIdle()); |
| } |