blob: 2773de051169169d34d9eda3a99fc89bcd6fe780 [file] [log] [blame]
// 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