blob: 381ea2be00df8c9bf469a9bf4eadd5d0b236580a [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 <zircon/status.h>
#include <utility>
#include "binding_internal.h"
#include "coordinator.h"
#include "fidl.h"
#include "src/devices/lib/log/log.h"
// CompositeDevice methods
CompositeDevice::CompositeDevice(fbl::String name, fbl::Array<const zx_device_prop_t> properties,
uint32_t fragments_count, uint32_t coresident_device_index,
fbl::Array<std::unique_ptr<Metadata>> metadata)
: name_(std::move(name)),
properties_(std::move(properties)),
fragments_count_(fragments_count),
coresident_device_index_(coresident_device_index),
metadata_(std::move(metadata)) {}
CompositeDevice::~CompositeDevice() = default;
zx_status_t CompositeDevice::Create(
const fbl::StringPiece& name,
llcpp::fuchsia::device::manager::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()],
comp_desc.props.count());
memcpy(properties.data(), comp_desc.props.data(),
comp_desc.props.count() * sizeof(comp_desc.props.data()[0]));
fbl::Array<std::unique_ptr<Metadata>> metadata(
new std::unique_ptr<Metadata>[comp_desc.metadata.count()], comp_desc.metadata.count());
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), comp_desc.fragments.count(),
comp_desc.coresident_device_index, std::move(metadata));
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;
fbl::Array<FragmentPartDescriptor> parts(new FragmentPartDescriptor[parts_count], parts_count);
for (size_t j = 0; j < parts_count; ++j) {
const auto& fidl_part = fidl_fragment.parts[j];
size_t program_count = fidl_part.match_program_count;
fbl::Array<zx_bind_inst_t> match_program(new zx_bind_inst_t[program_count], program_count);
for (size_t k = 0; k < program_count; ++k) {
match_program[k] = zx_bind_inst_t{
.op = fidl_part.match_program[k].op,
.arg = fidl_part.match_program[k].arg,
};
}
parts[j] = {std::move(match_program)};
}
std::string name(fidl_fragment.name.data(), fidl_fragment.name.size());
auto fragment = std::make_unique<CompositeDeviceFragment>(dev.get(), name, i, std::move(parts));
dev->unbound_.push_back(std::move(fragment));
}
*out = std::move(dev);
return ZX_OK;
}
bool CompositeDevice::TryMatchFragments(const fbl::RefPtr<Device>& dev, size_t* index_out) {
for (auto itr = bound_.begin(); itr != bound_.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_.begin(); itr != unbound_.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::BindFragment(size_t index, const fbl::RefPtr<Device>& dev) {
// Find the fragment we're binding
CompositeDeviceFragment* fragment = nullptr;
for (auto& unbound_fragment : unbound_) {
if (unbound_fragment.index() == index) {
fragment = &unbound_fragment;
break;
}
}
ZX_ASSERT_MSG(fragment != nullptr, "Attempted to bind fragment that wasn't unbound!\n");
zx_status_t status = fragment->Bind(dev);
if (status != ZX_OK) {
return status;
}
bound_.push_back(unbound_.erase(*fragment));
return ZX_OK;
}
zx_status_t CompositeDevice::TryAssemble() {
ZX_ASSERT(device_ == nullptr);
if (!unbound_.is_empty()) {
return ZX_ERR_SHOULD_WAIT;
}
fbl::RefPtr<DriverHost> driver_host;
for (auto& fragment : bound_) {
// Find the driver_host to put everything in (if we don't find one, nullptr
// means "a new driver_host").
if (fragment.index() == coresident_device_index_) {
driver_host = fragment.bound_device()->host();
}
// Make sure the fragment driver has created its device
if (fragment.fragment_device() == nullptr) {
return ZX_ERR_SHOULD_WAIT;
}
}
Coordinator* coordinator = nullptr;
std::pair<std::string_view, uint64_t> fragments[fuchsia_device_manager_FRAGMENTS_MAX] = {};
// Create all of the proxies for the fragment devices, in the same process
for (auto& fragment : bound_) {
const auto& fragment_dev = fragment.fragment_device();
auto bound_dev = fragment.bound_device();
coordinator = fragment_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();
}
// Check if we need to use the proxy. If not, share a reference to
// the instance of the fragment device.
if (bound_dev->host() == driver_host) {
fragments[fragment.index()].first = fragment.name();
fragments[fragment.index()].second = 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.
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;
}
zx_status_t status = coordinator->PrepareProxy(fragment_dev, driver_host);
if (status != ZX_OK) {
return status;
}
// If we hadn't picked a driver_host, use the one that was created just now.
if (driver_host == nullptr) {
driver_host = fragment_dev->proxy()->host();
ZX_ASSERT(driver_host != nullptr);
}
// Stash the local ID after the proxy has been created
fragments[fragment.index()].first = fragment.name();
fragments[fragment.index()].second = fragment_dev->proxy()->local_id();
}
zx::channel coordinator_rpc_local, coordinator_rpc_remote;
zx_status_t status = zx::channel::create(0, &coordinator_rpc_local, &coordinator_rpc_remote);
if (status != ZX_OK) {
return status;
}
zx::channel device_controller_rpc_local, device_controller_rpc_remote;
status = zx::channel::create(0, &device_controller_rpc_local, &device_controller_rpc_remote);
if (status != ZX_OK) {
return status;
}
fbl::RefPtr<Device> new_device;
status =
Device::CreateComposite(coordinator, driver_host, *this, std::move(coordinator_rpc_local),
std::move(device_controller_rpc_remote), &new_device);
if (status != ZX_OK) {
return status;
}
coordinator->devices().push_back(new_device);
// Create the composite device in the driver_host
status = dh_send_create_composite_device(driver_host, new_device.get(), *this, fragments,
std::move(coordinator_rpc_remote),
std::move(device_controller_rpc_local));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to create composite device: %s", zx_status_get_string(status));
return 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;
}
return ZX_OK;
}
void CompositeDevice::UnbindFragment(CompositeDeviceFragment* fragment) {
// If the composite was fully instantiated, diassociate 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_.push_back(bound_.erase(*fragment));
}
void CompositeDevice::Remove() {
device_->disassociate_from_composite();
device_ = nullptr;
}
// CompositeDeviceFragment methods
CompositeDeviceFragment::CompositeDeviceFragment(CompositeDevice* composite, std::string name,
uint32_t index,
fbl::Array<const FragmentPartDescriptor> parts)
: composite_(composite), name_(name), index_(index), parts_(std::move(parts)) {}
CompositeDeviceFragment::~CompositeDeviceFragment() = default;
bool CompositeDeviceFragment::TryMatch(const fbl::RefPtr<Device>& dev) {
if (parts_.size() > UINT32_MAX) {
return false;
}
auto match = ::internal::MatchParts(dev, parts_.data(), static_cast<uint32_t>(parts_.size()));
if (match != ::internal::Match::One) {
return false;
}
return true;
}
zx_status_t CompositeDeviceFragment::Bind(const fbl::RefPtr<Device>& dev) {
ZX_ASSERT(bound_device_ == nullptr);
zx_status_t status = dev->coordinator->BindDriverToDevice(
dev, dev->coordinator->fragment_driver(), true /* autobind */);
if (status != ZX_OK) {
return status;
}
bound_device_ = dev;
dev->push_fragment(this);
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;
}