blob: 200c9491252c12791f6deea788c945e4cbbc8cfb [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 "composite_device.h"
#include <lib/fidl/llcpp/arena.h>
#include <zircon/status.h>
#include <string_view>
#include <utility>
#include "binding_internal.h"
#include "coordinator.h"
#include "src/devices/lib/log/log.h"
namespace fdm = fuchsia_device_manager;
namespace {
fbl::Array<StrProperty> ConvertStringProperties(
fidl::VectorView<fdm::wire::DeviceStrProperty> str_props) {
fbl::Array<StrProperty> str_properties(new StrProperty[str_props.count()], str_props.count());
for (size_t i = 0; i < str_props.count(); i++) {
str_properties[i].key = str_props[i].key.get();
if (str_props[i].value.is_int_value()) {
str_properties[i].value.emplace<StrPropValueType::Integer>(str_props[i].value.int_value());
} else if (str_props[i].value.is_str_value()) {
str_properties[i].value.emplace<StrPropValueType::String>(
std::string(str_props[i].value.str_value().get()));
} else if (str_props[i].value.is_bool_value()) {
str_properties[i].value.emplace<StrPropValueType::Bool>(str_props[i].value.bool_value());
} else if (str_props[i].value.is_enum_value()) {
str_properties[i].value.emplace<StrPropValueType::Enum>(
std::string(str_props[i].value.enum_value().get()));
}
}
return str_properties;
}
} // namespace
// CompositeDevice methods
CompositeDevice::CompositeDevice(fbl::String name, fbl::Array<const zx_device_prop_t> properties,
fbl::Array<const StrProperty> str_properties,
uint32_t fragments_count, uint32_t primary_fragment_index,
bool spawn_colocated,
fbl::Array<std::unique_ptr<Metadata>> metadata,
bool from_driver_index)
: name_(std::move(name)),
properties_(std::move(properties)),
str_properties_(std::move(str_properties)),
fragments_count_(fragments_count),
primary_fragment_index_(primary_fragment_index),
spawn_colocated_(spawn_colocated),
metadata_(std::move(metadata)),
from_driver_index_(from_driver_index) {}
CompositeDevice::~CompositeDevice() = default;
zx_status_t CompositeDevice::Create(std::string_view name,
fdm::wire::CompositeDeviceDescriptor comp_desc,
std::unique_ptr<CompositeDevice>* out) {
fbl::String name_obj(name);
fbl::Array<zx_device_prop_t> properties(new zx_device_prop_t[comp_desc.props.count() + 1],
comp_desc.props.count() + 1);
memcpy(properties.data(), comp_desc.props.data(),
comp_desc.props.count() * sizeof(comp_desc.props.data()[0]));
// Set a property unique to composite devices.
properties[comp_desc.props.count()].id = BIND_COMPOSITE;
properties[comp_desc.props.count()].value = 1;
fbl::Array<std::unique_ptr<Metadata>> metadata(
new std::unique_ptr<Metadata>[comp_desc.metadata.count()], comp_desc.metadata.count());
fbl::Array<StrProperty> str_properties = ConvertStringProperties(comp_desc.str_props);
for (size_t i = 0; i < comp_desc.metadata.count(); i++) {
std::unique_ptr<Metadata> md;
zx_status_t status = Metadata::Create(comp_desc.metadata[i].data.count(), &md);
if (status != ZX_OK) {
return status;
}
md->type = comp_desc.metadata[i].key;
md->length = comp_desc.metadata[i].data.count();
memcpy(md->Data(), comp_desc.metadata[i].data.data(), md->length);
metadata[i] = std::move(md);
}
auto dev = std::make_unique<CompositeDevice>(
std::move(name), std::move(properties), std::move(str_properties),
comp_desc.fragments.count(), comp_desc.primary_fragment_index, comp_desc.spawn_colocated,
std::move(metadata), false);
for (uint32_t i = 0; i < comp_desc.fragments.count(); ++i) {
const auto& fidl_fragment = comp_desc.fragments[i];
size_t parts_count = fidl_fragment.parts.count();
if (parts_count != 1) {
LOGF(ERROR, "Composite fragments with multiple parts are deprecated. %s has %zd parts.",
name.data(), parts_count);
return ZX_ERR_INVALID_ARGS;
}
const auto& fidl_part = fidl_fragment.parts[0];
size_t program_count = fidl_part.match_program.count();
fbl::Array<zx_bind_inst_t> bind_rules(new zx_bind_inst_t[program_count], program_count);
for (size_t j = 0; j < program_count; ++j) {
bind_rules[j] = zx_bind_inst_t{
.op = fidl_part.match_program[j].op,
.arg = fidl_part.match_program[j].arg,
};
}
std::string name(fidl_fragment.name.data(), fidl_fragment.name.size());
auto fragment =
std::make_unique<CompositeDeviceFragment>(dev.get(), name, i, std::move(bind_rules));
dev->unbound_fragments_.push_back(std::move(fragment));
}
*out = std::move(dev);
return ZX_OK;
}
zx::status<std::unique_ptr<CompositeDevice>> CompositeDevice::CreateForDeviceGroup(
std::string_view name, fuchsia_device_manager::wire::DeviceGroupDescriptor group_desc) {
fbl::String name_obj(name);
fbl::Array<zx_device_prop_t> properties(new zx_device_prop_t[group_desc.props.count() + 1],
group_desc.props.count() + 1);
memcpy(properties.data(), group_desc.props.data(),
group_desc.props.count() * sizeof(group_desc.props.data()[0]));
// Set a property unique to composite devices.
properties[group_desc.props.count()].id = BIND_COMPOSITE;
properties[group_desc.props.count()].value = 1;
fbl::Array<std::unique_ptr<Metadata>> metadata(
new std::unique_ptr<Metadata>[group_desc.metadata.count()], group_desc.metadata.count());
fbl::Array<StrProperty> str_properties = ConvertStringProperties(group_desc.str_props);
for (size_t i = 0; i < group_desc.metadata.count(); i++) {
std::unique_ptr<Metadata> md;
zx_status_t status = Metadata::Create(group_desc.metadata[i].data.count(), &md);
if (status != ZX_OK) {
return zx::error(status);
;
}
md->type = group_desc.metadata[i].key;
md->length = group_desc.metadata[i].data.count();
memcpy(md->Data(), group_desc.metadata[i].data.data(), md->length);
metadata[i] = std::move(md);
}
auto dev = std::make_unique<CompositeDevice>(
std::move(name), std::move(properties), std::move(str_properties),
group_desc.fragments.count(), 0, group_desc.spawn_colocated, std::move(metadata), false);
for (uint32_t i = 0; i < group_desc.fragments.count(); ++i) {
const auto& node = group_desc.fragments[i];
std::string name(node.name.data(), node.name.size());
auto fragment = std::make_unique<CompositeDeviceFragment>(dev.get(), name, i,
fbl::Array<const zx_bind_inst_t>());
dev->unbound_fragments_.push_back(std::move(fragment));
}
return zx::ok(std::move(dev));
}
zx_status_t CompositeDevice::CreateFromDriverIndex(MatchedCompositeDriverInfo driver,
std::unique_ptr<CompositeDevice>* out) {
fbl::String name(driver.composite.name);
auto dev = std::make_unique<CompositeDevice>(
std::move(name), fbl::Array<const zx_device_prop_t>(), fbl::Array<const StrProperty>(),
driver.composite.num_nodes, 0, driver.driver_info.colocate,
fbl::Array<std::unique_ptr<Metadata>>(), true);
for (uint32_t i = 0; i < driver.composite.num_nodes; ++i) {
std::string name = driver.composite.node_names[i];
auto fragment = std::make_unique<CompositeDeviceFragment>(dev.get(), std::string(name), i,
fbl::Array<const zx_bind_inst_t>());
dev->unbound_fragments_.push_back(std::move(fragment));
}
dev->driver_index_driver_ = driver.driver_info.driver;
*out = std::move(dev);
return ZX_OK;
}
bool CompositeDevice::IsFragmentMatch(const fbl::RefPtr<Device>& dev, size_t* index_out) const {
if (from_driver_index_) {
return false;
}
for (auto itr = bound_fragments_.begin(); itr != bound_fragments_.end(); ++itr) {
if (itr->TryMatch(dev)) {
LOGF(ERROR, "Ambiguous bind for composite device %p '%s': device 1 '%s', device 2 '%s'", this,
name_.data(), itr->bound_device()->name().data(), dev->name().data());
return false;
}
}
for (auto itr = unbound_fragments_.begin(); itr != unbound_fragments_.end(); ++itr) {
if (itr->TryMatch(dev)) {
VLOGF(1, "Found a match for composite device %p '%s': device '%s'", this, name_.data(),
dev->name().data());
*index_out = itr->index();
return true;
}
}
VLOGF(1, "No match for composite device %p '%s': device '%s'", this, name_.data(),
dev->name().data());
return false;
}
zx_status_t CompositeDevice::TryMatchBindFragments(const fbl::RefPtr<Device>& dev) {
size_t index;
if (!IsFragmentMatch(dev, &index)) {
return ZX_OK;
}
LOGF(INFO, "Device '%s' matched fragment %zu of composite '%s'", dev->name().data(), index,
name().data());
auto status = BindFragment(index, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Device '%s' failed to bind fragment %zu of composite '%s': %s", dev->name().data(),
index, name().data(), zx_status_get_string(status));
}
return status;
}
zx_status_t CompositeDevice::BindFragment(size_t index, const fbl::RefPtr<Device>& dev) {
// Find the fragment we're binding
CompositeDeviceFragment* fragment = nullptr;
for (auto& unbound_fragment : unbound_fragments_) {
if (unbound_fragment.index() == index) {
fragment = &unbound_fragment;
break;
}
}
if (!fragment) {
LOGF(ERROR, "Attempted to bind bound fragment %zu in composite device %p", index, name_.data());
return ZX_OK;
}
zx_status_t status = fragment->Bind(dev);
if (status != ZX_OK) {
return status;
}
bound_fragments_.push_back(unbound_fragments_.erase(*fragment));
if (dev->has_outgoing_directory()) {
TryAssemble();
}
return ZX_OK;
}
zx_status_t CompositeDevice::TryAssemble() {
ZX_ASSERT(device_ == nullptr);
if (!unbound_fragments_.is_empty()) {
return ZX_ERR_SHOULD_WAIT;
}
for (auto& fragment : bound_fragments_) {
// Make sure the fragment driver has created its device
// or that it didn't need one.
if (fragment.fragment_device() == nullptr &&
!fragment.bound_device()->has_outgoing_directory()) {
return ZX_ERR_SHOULD_WAIT;
}
}
// Find the driver_host to put everything in, nullptr means "a new driver_host".
fbl::RefPtr<DriverHost> driver_host;
if (spawn_colocated_) {
for (auto& fragment : bound_fragments_) {
if (fragment.index() == primary_fragment_index_) {
driver_host = fragment.bound_device()->host();
}
}
}
Coordinator* coordinator = nullptr;
fidl::Arena allocator;
fidl::VectorView<fdm::wire::Fragment> fragments(allocator, bound_fragments_.size_slow());
// Create all of the proxies for the fragment devices, in the same process
for (auto& fragment : bound_fragments_) {
const auto& fragment_dev = fragment.fragment_device();
auto bound_dev = fragment.bound_device();
coordinator = bound_dev->coordinator;
// If the device we're bound to is proxied, we care about its proxy
// rather than it, since that's the side that we communicate with.
if (bound_dev->proxy()) {
bound_dev = bound_dev->proxy();
}
if (bound_dev->new_proxy()) {
bound_dev = bound_dev->new_proxy();
}
// Check if we need to use the proxy. If not, share a reference to
// the instance of the fragment device.
// We always use a proxy when there is an outgoing directory involved.
if (bound_dev->host() == driver_host && !bound_dev->has_outgoing_directory()) {
fragments[fragment.index()].name = fidl::StringView::FromExternal(fragment.name());
fragments[fragment.index()].id = fragment_dev->local_id();
continue;
}
// We need to create it. Double check that we haven't ended up in a state
// where the proxies would need to be in different processes.
fbl::RefPtr<Device> proxy;
zx_status_t status;
if (bound_dev->has_outgoing_directory()) {
if (driver_host != nullptr && bound_dev->new_proxy() != nullptr &&
bound_dev->new_proxy()->host() != nullptr &&
bound_dev->new_proxy()->host() != driver_host) {
LOGF(ERROR, "Cannot create composite device, device proxies are in different driver_hosts");
return ZX_ERR_BAD_STATE;
}
VLOGF(1, "Preparing new proxy for %s", bound_dev->name().data());
status = coordinator->PrepareNewProxy(bound_dev, driver_host);
if (status != ZX_OK) {
return status;
}
proxy = bound_dev->new_proxy();
} else {
if (driver_host != nullptr && fragment_dev->proxy() != nullptr &&
fragment_dev->proxy()->host() != nullptr &&
fragment_dev->proxy()->host() != driver_host) {
LOGF(ERROR, "Cannot create composite device, device proxies are in different driver_hosts");
return ZX_ERR_BAD_STATE;
}
VLOGF(1, "Preparing old proxy for %s", fragment_dev->name().data());
status = coordinator->PrepareProxy(fragment_dev, driver_host);
if (status != ZX_OK) {
return status;
}
proxy = fragment_dev->proxy();
}
// If we hadn't picked a driver_host, use the one that was created just now.
if (driver_host == nullptr) {
driver_host = proxy->host();
ZX_ASSERT(driver_host != nullptr);
}
// Stash the local ID after the proxy has been created
fragments[fragment.index()].name = fidl::StringView::FromExternal(fragment.name());
fragments[fragment.index()].id = proxy->local_id();
}
auto coordinator_endpoints = fidl::CreateEndpoints<fdm::Coordinator>();
if (coordinator_endpoints.is_error()) {
return coordinator_endpoints.error_value();
}
auto device_controller_endpoints = fidl::CreateEndpoints<fdm::DeviceController>();
if (device_controller_endpoints.is_error()) {
return device_controller_endpoints.error_value();
}
fbl::RefPtr<Device> new_device;
auto status = Device::CreateComposite(
coordinator, driver_host, *this, std::move(coordinator_endpoints->server),
std::move(device_controller_endpoints->client), &new_device);
if (status != ZX_OK) {
return status;
}
coordinator->device_manager()->AddToDevices(new_device);
// Create the composite device in the driver_host
fdm::wire::CompositeDevice composite{fragments, fidl::StringView::FromExternal(name())};
auto type = fdm::wire::DeviceType::WithComposite(allocator, composite);
driver_host->controller()
->CreateDevice(std::move(coordinator_endpoints->client),
std::move(device_controller_endpoints->server), std::move(type),
new_device->local_id())
.ThenExactlyOnce(
[](fidl::WireUnownedResult<fdm::DriverHostController::CreateDevice>& result) {
if (!result.ok()) {
LOGF(ERROR, "Failed to create composite device: %s",
result.error().FormatDescription().c_str());
return;
}
if (result.value().status != ZX_OK) {
LOGF(ERROR, "Failed to create composite device: %s",
zx_status_get_string(result.value().status));
}
});
device_ = std::move(new_device);
device_->set_composite(this);
// Add metadata
for (size_t i = 0; i < metadata_.size(); i++) {
// Making a copy of metadata, instead of transfering ownership, so that
// metadata can be added again if device is recreated
status = coordinator->AddMetadata(device_, metadata_[i]->type, metadata_[i]->Data(),
metadata_[i]->length);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to add metadata to device %p '%s': %s", device_.get(),
device_->name().data(), zx_status_get_string(status));
return status;
}
}
status = device_->SignalReadyForBind();
if (status != ZX_OK) {
return status;
}
if (from_driver_index_) {
zx_status_t status = coordinator->AttemptBind(driver_index_driver_, device_);
if (status != ZX_OK) {
LOGF(ERROR, "%s: Failed to bind composite driver '%s' to device '%s': %s", __func__,
driver_index_driver_->libname.data(), device_->name().data(),
zx_status_get_string(status));
}
return status;
}
return ZX_OK;
}
void CompositeDevice::UnbindFragment(CompositeDeviceFragment* fragment) {
// If the composite was fully instantiated, disassociate from it. It will be
// reinstantiated when this fragment is re-bound.
if (device_ != nullptr) {
Remove();
}
ZX_ASSERT(device_ == nullptr);
ZX_ASSERT(fragment->composite() == this);
unbound_fragments_.push_back(bound_fragments_.erase(*fragment));
}
void CompositeDevice::Remove() {
if (device_ != nullptr) {
device_->disassociate_from_composite();
device_ = nullptr;
}
}
// CompositeDeviceFragment methods
CompositeDeviceFragment::CompositeDeviceFragment(CompositeDevice* composite, std::string name,
uint32_t index,
fbl::Array<const zx_bind_inst_t> bind_rules)
: composite_(composite), name_(name), index_(index), bind_rules_(std::move(bind_rules)) {}
CompositeDeviceFragment::~CompositeDeviceFragment() = default;
bool CompositeDeviceFragment::TryMatch(const fbl::RefPtr<Device>& dev) const {
return internal::EvaluateBindProgram(dev, "composite_binder", bind_rules_, true /* autobind */);
}
zx_status_t CompositeDeviceFragment::Bind(const fbl::RefPtr<Device>& dev) {
ZX_ASSERT(bound_device_ == nullptr);
if (!dev->has_outgoing_directory()) {
zx_status_t status = dev->coordinator->bind_driver_manager()->MatchAndBind(
dev, dev->coordinator->fragment_driver(), false /* autobind */);
if (status != ZX_OK) {
return status;
}
bound_device_ = dev;
dev->push_fragment(this);
} else {
bound_device_ = dev;
dev->push_fragment(this);
dev->flags |= DEV_CTX_BOUND;
}
return ZX_OK;
}
void CompositeDeviceFragment::Unbind() {
ZX_ASSERT(bound_device_ != nullptr);
composite_->UnbindFragment(this);
// Drop our reference to the device added by the fragment driver
fragment_device_ = nullptr;
bound_device_->disassociate_from_composite();
bound_device_ = nullptr;
}