| // Copyright 2022 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/driver/compat/cpp/device_server.h> |
| #include <lib/driver/component/cpp/node_add_args.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/fdio/directory.h> |
| |
| namespace compat { |
| |
| namespace { |
| |
| void CollectMetadataFrom(fidl::VectorView<fuchsia_driver_compat::wire::Metadata> metadata, |
| const ForwardMetadata& forward_metadata, DeviceServer* server) { |
| for (auto& metadata : metadata) { |
| auto should_forward = forward_metadata.should_forward(metadata.type); |
| if (should_forward) { |
| size_t size; |
| zx_status_t status = |
| metadata.data.get_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size)); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to get metadata vmo size: %s", zx_status_get_string(status)); |
| continue; |
| } |
| |
| Metadata data(size); |
| status = metadata.data.read(data.data(), 0, data.size()); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to read metadata vmo: %s", zx_status_get_string(status)); |
| continue; |
| } |
| |
| server->AddMetadata(metadata.type, data.data(), data.size()); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| ForwardMetadata ForwardMetadata::All() { return ForwardMetadata(std::nullopt); } |
| |
| ForwardMetadata ForwardMetadata::None() { |
| return ForwardMetadata(std::make_optional(std::unordered_set<MetadataKey>{})); |
| } |
| |
| ForwardMetadata ForwardMetadata::Some(std::unordered_set<MetadataKey> filter) { |
| ZX_ASSERT_MSG(!filter.empty(), "ForwardMetadata::Some 'filter' cannot be empty."); |
| return ForwardMetadata(std::make_optional(filter)); |
| } |
| |
| bool ForwardMetadata::empty() const { return filter_.has_value() && filter_->empty(); } |
| |
| bool ForwardMetadata::should_forward(MetadataKey key) const { |
| if (!filter_.has_value()) { |
| return true; |
| } |
| |
| return filter_->find(key) != filter_->end(); |
| } |
| |
| void DeviceServer::Initialize(std::string name, std::optional<ServiceOffersV1> service_offers, |
| std::optional<BanjoConfig> banjo_config) { |
| name_ = std::move(name); |
| service_offers_ = std::move(service_offers); |
| banjo_config_ = std::move(banjo_config); |
| } |
| |
| zx_status_t DeviceServer::AddMetadata(uint32_t type, const void* data, size_t size) { |
| Metadata metadata(size); |
| auto begin = static_cast<const uint8_t*>(data); |
| std::copy(begin, begin + size, metadata.begin()); |
| auto [_, inserted] = metadata_.emplace(type, std::move(metadata)); |
| if (!inserted) { |
| // TODO(https://fxbug.dev/42063857): Return ZX_ERR_ALREADY_EXISTS instead once we do so in DFv1. |
| return ZX_OK; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceServer::GetMetadata(uint32_t type, void* buf, size_t buflen, size_t* actual) { |
| auto it = metadata_.find(type); |
| if (it == metadata_.end()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| auto& [_, metadata] = *it; |
| |
| *actual = metadata.size(); |
| if (buflen < metadata.size()) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| auto size = std::min(buflen, metadata.size()); |
| auto begin = metadata.begin(); |
| std::copy(begin, begin + size, static_cast<uint8_t*>(buf)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceServer::GetMetadataSize(uint32_t type, size_t* out_size) { |
| auto it = metadata_.find(type); |
| if (it == metadata_.end()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| auto& [_, metadata] = *it; |
| *out_size = metadata.size(); |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceServer::GetProtocol(BanjoProtoId proto_id, GenericProtocol* out) const { |
| if (!banjo_config_.has_value()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // If there is a specific entry for the proto_id, use it. |
| auto specific_entry = banjo_config_->callbacks.find(proto_id); |
| if (specific_entry != banjo_config_->callbacks.end()) { |
| auto& get_banjo_protocol = specific_entry->second; |
| if (out) { |
| *out = get_banjo_protocol(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Otherwise use the generic one if one was provided. |
| if (!banjo_config_->generic_callback) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx::result generic_result = banjo_config_->generic_callback(proto_id); |
| if (generic_result.is_error()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| if (out) { |
| *out = generic_result.value(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DeviceServer::Serve(async_dispatcher_t* dispatcher, fdf::OutgoingDirectory* outgoing) { |
| auto device = [this, dispatcher]( |
| fidl::ServerEnd<fuchsia_driver_compat::Device> server_end) mutable -> void { |
| bindings_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure); |
| }; |
| fuchsia_driver_compat::Service::InstanceHandler handler({.device = device}); |
| zx::result<> status = |
| outgoing->AddService<fuchsia_driver_compat::Service>(std::move(handler), name()); |
| if (status.is_error()) { |
| return status.error_value(); |
| } |
| stop_serving_ = [this, outgoing]() { |
| (void)outgoing->RemoveService<fuchsia_driver_compat::Service>(name_); |
| }; |
| |
| if (service_offers_) { |
| return service_offers_->Serve(dispatcher, outgoing); |
| } |
| return ZX_OK; |
| } |
| |
| std::vector<fuchsia_driver_framework::wire::Offer> DeviceServer::CreateOffers2( |
| fidl::ArenaBase& arena) { |
| std::vector<fuchsia_driver_framework::wire::Offer> offers; |
| // Create the main fuchsia.driver.compat.Service offer. |
| offers.push_back(fdf::MakeOffer2<fuchsia_driver_compat::Service>(arena, name())); |
| |
| if (service_offers_) { |
| auto service_offers = service_offers_->CreateOffers2(arena); |
| offers.reserve(offers.size() + service_offers.size()); |
| offers.insert(offers.end(), service_offers.begin(), service_offers.end()); |
| } |
| return offers; |
| } |
| |
| std::vector<fuchsia_driver_framework::Offer> DeviceServer::CreateOffers2() { |
| std::vector<fuchsia_driver_framework::Offer> offers; |
| // Create the main fuchsia.driver.compat.Service offer. |
| offers.push_back(fdf::MakeOffer2<fuchsia_driver_compat::Service>(name())); |
| |
| if (service_offers_) { |
| auto service_offers = service_offers_->CreateOffers2(); |
| offers.reserve(offers.size() + service_offers.size()); |
| offers.insert(offers.end(), service_offers.begin(), service_offers.end()); |
| } |
| return offers; |
| } |
| |
| void DeviceServer::GetMetadata(GetMetadataCompleter::Sync& completer) { |
| std::vector<fuchsia_driver_compat::wire::Metadata> metadata; |
| metadata.reserve(metadata_.size()); |
| for (auto& [type, data] : metadata_) { |
| fuchsia_driver_compat::wire::Metadata new_metadata; |
| new_metadata.type = type; |
| zx::vmo vmo; |
| |
| zx_status_t status = zx::vmo::create(data.size(), 0, &new_metadata.data); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| status = new_metadata.data.write(data.data(), 0, data.size()); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| size_t size = data.size(); |
| status = new_metadata.data.set_property(ZX_PROP_VMO_CONTENT_SIZE, &size, sizeof(size)); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| |
| metadata.push_back(std::move(new_metadata)); |
| } |
| completer.ReplySuccess(fidl::VectorView<fuchsia_driver_compat::wire::Metadata>::FromExternal( |
| metadata.data(), metadata.size())); |
| } |
| |
| void DeviceServer::GetBanjoProtocol(GetBanjoProtocolRequestView request, |
| GetBanjoProtocolCompleter::Sync& completer) { |
| // First check that we are in the same driver host. |
| static uint64_t process_koid = []() { |
| zx_info_handle_basic_t basic; |
| ZX_ASSERT(zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &basic, sizeof(basic), nullptr, |
| nullptr) == ZX_OK); |
| return basic.koid; |
| }(); |
| |
| if (process_koid != request->process_koid) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| |
| GenericProtocol result; |
| zx_status_t status = GetProtocol(request->proto_id, &result); |
| if (status != ZX_OK) { |
| completer.ReplyError(status); |
| return; |
| } |
| |
| completer.ReplySuccess(reinterpret_cast<uint64_t>(result.ops), |
| reinterpret_cast<uint64_t>(result.ctx)); |
| } |
| |
| zx::result<> SyncInitializedDeviceServer::Initialize( |
| const std::shared_ptr<fdf::Namespace>& incoming, |
| const std::shared_ptr<fdf::OutgoingDirectory>& outgoing, |
| const std::optional<std::string>& node_name, std::string_view child_node_name, |
| const ForwardMetadata& forward_metadata, std::optional<DeviceServer::BanjoConfig> banjo_config, |
| const std::optional<std::string>& child_additional_path) { |
| auto child_node_name_str = std::string(child_node_name); |
| |
| // We can just serve and return if no metadata is needed. |
| if (forward_metadata.empty()) { |
| return CreateAndServe(outgoing, child_node_name_str, std::move(banjo_config)); |
| } |
| |
| // First connect to all the parents. |
| auto parent_devices = ConnectToParentDevices(incoming.get()); |
| if (parent_devices.is_error()) { |
| FDF_LOG(DEBUG, "Failed to get parent devices: %s.", parent_devices.status_string()); |
| |
| // No parents if we are the root node. |
| return CreateAndServe(outgoing, child_node_name_str, std::move(banjo_config)); |
| } |
| |
| // Now we have out parent clients, store them. |
| fidl::WireSyncClient<fuchsia_driver_compat::Device> default_parent_client; |
| std::unordered_map<std::string, fidl::WireSyncClient<fuchsia_driver_compat::Device>> |
| parent_clients; |
| for (auto& parent : parent_devices.value()) { |
| if (parent.name == "default") { |
| default_parent_client.Bind(std::move(parent.client)); |
| continue; |
| } |
| |
| // TODO(https://fxbug.dev/42051759): When services stop adding extra instances |
| // separated by ',' then remove this check. |
| if (parent.name.find(',') != std::string::npos) { |
| continue; |
| } |
| |
| parent_clients[parent.name] = |
| fidl::WireSyncClient<fuchsia_driver_compat::Device>(std::move(parent.client)); |
| } |
| |
| // No default parent found. |
| if (!default_parent_client.is_valid()) { |
| FDF_LOG(DEBUG, "Failed to find the default parent."); |
| |
| // No parents if we are the root node. |
| return CreateAndServe(outgoing, child_node_name_str, std::move(banjo_config)); |
| } |
| |
| // We will create and serve the inner DeviceServer so we can add the metadata to it. |
| auto result = CreateAndServe(outgoing, child_node_name_str, std::move(banjo_config)); |
| if (result.is_error()) { |
| return result; |
| } |
| |
| // Forward metadata |
| if (parent_clients.empty()) { |
| fidl::WireResult metadata_result = default_parent_client->GetMetadata(); |
| if (!metadata_result.ok()) { |
| FDF_LOG(WARNING, "Failed to get metadata from default parent. %s", |
| metadata_result.status_string()); |
| } else if (metadata_result.value().is_error()) { |
| FDF_LOG(WARNING, "Failed to get metadata from default parent. %s", |
| zx_status_get_string(metadata_result.value().error_value())); |
| return zx::ok(); |
| } else { |
| CollectMetadataFrom(metadata_result->value()->metadata, forward_metadata, |
| &device_server_.value()); |
| } |
| } else { |
| for (auto& [parent_name, parent_client] : parent_clients) { |
| fidl::WireResult metadata_result = parent_client->GetMetadata(); |
| if (!metadata_result.ok()) { |
| FDF_LOG(WARNING, "Failed to get metadata from parent %s. %s", parent_name.c_str(), |
| metadata_result.status_string()); |
| } else if (metadata_result.value().is_error()) { |
| FDF_LOG(WARNING, "Failed to get metadata from parent %s. %s", parent_name.c_str(), |
| zx_status_get_string(metadata_result.value().error_value())); |
| } else { |
| CollectMetadataFrom(metadata_result->value()->metadata, forward_metadata, |
| &device_server_.value()); |
| } |
| } |
| } |
| |
| return zx::ok(); |
| } |
| |
| zx::result<> SyncInitializedDeviceServer::CreateAndServe( |
| const std::shared_ptr<fdf::OutgoingDirectory>& outgoing, std::string child_node_name, |
| std::optional<DeviceServer::BanjoConfig> banjo_config) { |
| ZX_ASSERT_MSG(device_server_ == std::nullopt, |
| "Cannot call Initialize on the SyncInitializedDeviceServer more than once."); |
| device_server_.emplace(); |
| device_server_->Initialize(std::move(child_node_name), std::nullopt, std::move(banjo_config)); |
| |
| zx_status_t serve_result = |
| device_server_->Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), outgoing.get()); |
| if (serve_result != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to serve: %s", zx_status_get_string(serve_result)); |
| device_server_.reset(); |
| return zx::error(serve_result); |
| } |
| |
| return zx::ok(); |
| } |
| |
| void AsyncInitializedDeviceServer::Begin(const std::shared_ptr<fdf::Namespace>& incoming, |
| const std::shared_ptr<fdf::OutgoingDirectory>& outgoing, |
| const std::optional<std::string>& node_name, |
| std::string_view child_node_name, |
| fit::callback<void(zx::result<>)> callback, |
| const ForwardMetadata& forward_metadata, |
| std::optional<DeviceServer::BanjoConfig> banjo_config, |
| const std::optional<std::string>& child_additional_path) { |
| ZX_ASSERT_MSG(storage_ == std::nullopt, |
| "Cannot call Begin on AsyncInitializedDeviceServer more than once."); |
| storage_.emplace(AsyncInitStorage{ |
| .incoming = incoming, |
| .outgoing = outgoing, |
| .child_node_name = std::string(child_node_name), |
| .callback = std::move(callback), |
| .forward_metadata = forward_metadata, |
| .banjo_config = std::move(banjo_config), |
| }); |
| BeginAsyncInit(); |
| } |
| |
| void AsyncInitializedDeviceServer::BeginAsyncInit() { |
| ZX_ASSERT(storage_); |
| if (storage_->forward_metadata.empty()) { |
| OnParentDevices(zx::ok(std::vector<ParentDevice>{})); |
| return; |
| } |
| |
| auto task = ConnectToParentDevices(fdf::Dispatcher::GetCurrent()->async_dispatcher(), |
| storage_->incoming.get(), |
| [this](zx::result<std::vector<ParentDevice>> parents) { |
| OnParentDevices(std::move(parents)); |
| }); |
| async_tasks_.AddTask(std::move(task)); |
| } |
| |
| void AsyncInitializedDeviceServer::OnParentDevices( |
| zx::result<std::vector<ParentDevice>> parent_devices) { |
| if (!storage_) { |
| return; |
| } |
| |
| if (parent_devices.is_error()) { |
| FDF_LOG(DEBUG, "Failed to get parent devices: %s.", parent_devices.status_string()); |
| |
| // No parents if we are the root node. |
| zx::result result = CreateAndServe(); |
| if (result.is_error()) { |
| CompleteInitialization(result.take_error()); |
| return; |
| } |
| |
| CompleteInitialization(zx::ok()); |
| return; |
| } |
| |
| for (auto& parent : parent_devices.value()) { |
| if (parent.name == "default") { |
| default_parent_client_.Bind(std::move(parent.client), |
| fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| continue; |
| } |
| |
| // TODO(https://fxbug.dev/42051759): When services stop adding extra instances |
| // separated by ',' then remove this check. |
| if (parent.name.find(',') != std::string::npos) { |
| continue; |
| } |
| |
| parent_clients_[parent.name] = fidl::WireClient<fuchsia_driver_compat::Device>( |
| std::move(parent.client), fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| } |
| |
| if (!default_parent_client_.is_valid()) { |
| FDF_LOG(DEBUG, "Failed to find the default parent."); |
| |
| // No parents if we are the root node. |
| zx::result result = CreateAndServe(); |
| if (result.is_error()) { |
| CompleteInitialization(result.take_error()); |
| return; |
| } |
| |
| CompleteInitialization(zx::ok()); |
| return; |
| } |
| |
| // We will create and serve the inner DeviceServer so we can add the metadata to it. |
| zx::result create_result = CreateAndServe(); |
| if (create_result.is_error()) { |
| CompleteInitialization(create_result.take_error()); |
| return; |
| } |
| |
| if (parent_clients_.empty()) { |
| storage_->in_flight_metadata++; |
| default_parent_client_->GetMetadata().Then( |
| [this](fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetMetadata>& result) { |
| OnMetadataResult(result); |
| }); |
| } else { |
| for (auto& [parent_name, parent_client] : parent_clients_) { |
| storage_->in_flight_metadata++; |
| parent_client->GetMetadata().Then( |
| [this](fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetMetadata>& result) { |
| OnMetadataResult(result); |
| }); |
| } |
| } |
| } |
| |
| void AsyncInitializedDeviceServer::OnMetadataResult( |
| fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetMetadata>& result) { |
| if (!storage_) { |
| return; |
| } |
| |
| if (!result.ok()) { |
| FDF_LOG(WARNING, "Failed to get metadata: %s", result.status_string()); |
| } else if (result.value().is_error()) { |
| FDF_LOG(WARNING, "Failed to get metadata: %s", |
| zx_status_get_string(result.value().error_value())); |
| |
| } else { |
| CollectMetadataFrom(result.value().value()->metadata, storage_->forward_metadata, |
| &device_server_.value()); |
| } |
| |
| storage_->in_flight_metadata--; |
| if (storage_->in_flight_metadata == 0) { |
| CompleteInitialization(zx::ok()); |
| return; |
| } |
| } |
| |
| zx::result<> AsyncInitializedDeviceServer::CreateAndServe() { |
| if (!storage_) { |
| return zx::error(ZX_ERR_CANCELED); |
| } |
| |
| ZX_ASSERT(device_server_ == std::nullopt); |
| device_server_.emplace(); |
| device_server_->Initialize(std::move(storage_->child_node_name), std::nullopt, |
| std::move(storage_->banjo_config)); |
| |
| zx_status_t serve_result = device_server_->Serve( |
| fdf::Dispatcher::GetCurrent()->async_dispatcher(), storage_->outgoing.get()); |
| if (serve_result != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to serve: %s", zx_status_get_string(serve_result)); |
| device_server_.reset(); |
| return zx::error(serve_result); |
| } |
| |
| return zx::ok(); |
| } |
| |
| void AsyncInitializedDeviceServer::CompleteInitialization(zx::result<> result) { |
| if (result.is_error()) { |
| device_server_.reset(); |
| } |
| |
| if (!storage_) { |
| return; |
| } |
| |
| auto callback = std::move(storage_->callback); |
| storage_.reset(); |
| callback(result); |
| } |
| |
| } // namespace compat |