blob: 934de8ae195c3f3f1d944aacc40fe5fb69ecead6 [file] [log] [blame] [edit]
// 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 "compat.h"
#include <lib/driver2/devfs_exporter.h>
#include <lib/fdio/directory.h>
#include "lib/fpromise/promise.h"
namespace compat {
namespace fdf = fuchsia_driver_framework;
namespace fcd = fuchsia_component_decl;
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) {
return ZX_ERR_ALREADY_EXISTS;
}
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;
auto size = std::min(buflen, metadata.size());
auto begin = metadata.begin();
std::copy(begin, begin + size, static_cast<uint8_t*>(buf));
*actual = metadata.size();
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;
}
void DeviceServer::GetTopologicalPath(GetTopologicalPathRequestView request,
GetTopologicalPathCompleter::Sync& completer) {
completer.Reply(fidl::StringView::FromExternal(topological_path_));
}
void DeviceServer::GetMetadata(GetMetadataRequestView request,
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::ConnectFidl(ConnectFidlRequestView request,
ConnectFidlCompleter::Sync& completer) {
if (dir_.is_valid()) {
auto path = std::string("svc/").append(request->name.data(), request->name.size());
fdio_service_connect_at(dir_.channel().get(), path.data(), request->server.release());
}
completer.Reply();
}
zx::status<Interop> Interop::Create(async_dispatcher_t* dispatcher, const driver::Namespace* ns,
component::OutgoingDirectory* outgoing) {
Interop interop;
interop.dispatcher_ = dispatcher;
interop.ns_ = ns;
interop.outgoing_ = outgoing;
interop.vfs_ = std::make_unique<fs::SynchronousVfs>(dispatcher);
interop.devfs_exports_ = fbl::MakeRefCounted<fs::PseudoDir>();
interop.compat_service_ = fbl::MakeRefCounted<fs::PseudoDir>();
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
// Start the devfs exporter.
auto serve_status = interop.vfs_->Serve(interop.devfs_exports_, endpoints->server.TakeChannel(),
fs::VnodeConnectionOptions::ReadWrite());
auto exporter = driver::DevfsExporter::Create(
*interop.ns_, interop.dispatcher_,
fidl::WireSharedClient(std::move(endpoints->client), dispatcher));
if (exporter.is_error()) {
return zx::error(exporter.error_value());
}
interop.exporter_ = std::move(*exporter);
endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
serve_status = interop.vfs_->Serve(interop.compat_service_, endpoints->server.TakeChannel(),
fs::VnodeConnectionOptions::ReadWrite());
if (serve_status != ZX_OK) {
return zx::error(serve_status);
}
auto status = interop.outgoing_->AddDirectory(std::move(endpoints->client),
"fuchsia.driver.compat.Service");
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(interop));
}
zx::status<fidl::WireSharedClient<fuchsia_driver_compat::Device>> ConnectToParentDevice(
async_dispatcher_t* dispatcher, const driver::Namespace* ns, std::string_view name) {
auto result = ns->OpenService<fuchsia_driver_compat::Service>("default");
if (result.is_error()) {
return result.take_error();
}
auto connection = result.value().connect_device();
if (connection.is_error()) {
return connection.take_error();
}
return zx::ok(fidl::WireSharedClient<fuchsia_driver_compat::Device>(std::move(connection.value()),
dispatcher));
}
fpromise::promise<void, zx_status_t> Interop::ExportChild(Child* child,
fbl::RefPtr<fs::Vnode> dev_node) {
// Expose the fuchsia.driver.compat.Service instance.
service::ServiceHandler handler;
fuchsia_driver_compat::Service::Handler compat_service(&handler);
auto device = [this,
child](fidl::ServerEnd<fuchsia_driver_compat::Device> server_end) mutable -> void {
fidl::BindServer<fidl::WireServer<fuchsia_driver_compat::Device>>(
dispatcher_, std::move(server_end), &child->compat_device());
};
zx::status<> status = compat_service.add_device(std::move(device));
if (status.is_error()) {
return fpromise::make_error_promise(status.error_value());
}
compat_service_->AddEntry(child->name(), handler.TakeDirectory());
ServiceInstanceOffer instance_offer;
instance_offer.service_name = "fuchsia.driver.compat.Service";
instance_offer.instance_name = child->name();
instance_offer.renamed_instance_name = "default";
instance_offer.remove_service_callback = std::make_shared<fit::deferred_callback>(
[service = this->compat_service_, name = std::string(child->name())]() {
service->RemoveEntry(name);
});
child->offers().AddServiceInstance(std::move(instance_offer));
// Expose the child in /dev/.
if (!dev_node) {
return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok());
}
zx_status_t add_status = devfs_exports_->AddEntry(child->name(), dev_node);
if (add_status != ZX_OK) {
return fpromise::make_error_promise<zx_status_t>(add_status);
}
// If the child goes out of scope, we should close the devfs connection.
child->AddCallback(std::make_shared<fit::deferred_callback>(
[this, name = std::string(child->name()), dev_node]() {
(void)outgoing_->RemoveNamedProtocol(name);
vfs_->CloseAllConnectionsForVnode(*dev_node, {});
devfs_exports_->RemoveEntry(name);
}));
return exporter_.Export(child->name(), child->topological_path(), child->proto_id());
}
std::vector<fuchsia_component_decl::wire::Offer> Child::CreateOffers(fidl::ArenaBase& arena) {
return offers_.CreateOffers(arena);
}
std::vector<fuchsia_component_decl::wire::Offer> ChildOffers::CreateOffers(fidl::ArenaBase& arena) {
std::vector<fuchsia_component_decl::wire::Offer> offers;
for (auto& instance : instance_offers_) {
auto dir_offer = fcd::wire::OfferDirectory::Builder(arena);
dir_offer.source_name(arena, instance.service_name);
if (instance.renamed_instance_name) {
dir_offer.target_name(arena, instance.service_name + "-" + *instance.renamed_instance_name);
}
dir_offer.rights(fuchsia_io::wire::kRwStarDir);
dir_offer.subdir(arena, instance.instance_name);
dir_offer.dependency_type(fcd::wire::DependencyType::kStrong);
offers.push_back(fcd::wire::Offer::WithDirectory(arena, dir_offer.Build()));
}
// XXX: Do protocols.
return offers;
}
} // namespace compat