blob: a187735586233e4a9564555a70fb25b9b11ec654 [file] [log] [blame]
// Copyright 2017 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 "src/devices/bin/driver_manager/coordinator.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/boot/llcpp/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <fuchsia/pkg/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/receiver.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/ddk/driver.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <lib/fidl-async/bind.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/coding.h>
#include <lib/fit/defer.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/service/llcpp/service.h>
#include <lib/zircon-internal/ktrace.h>
#include <lib/zx/clock.h>
#include <lib/zx/job.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/policy.h>
#include <zircon/syscalls/system.h>
#include <cstdint>
#include <memory>
#include <string_view>
#include <utility>
#include <driver-info/driver-info.h>
#include <fbl/string_printf.h>
#include <inspector/inspector.h>
#include "driver_host_loader_service.h"
#include "fuchsia/hardware/power/statecontrol/llcpp/fidl.h"
#include "lib/zx/time.h"
#include "src/devices/bin/driver_manager/composite_device.h"
#include "src/devices/bin/driver_manager/devfs.h"
#include "src/devices/bin/driver_manager/driver_host_loader_service.h"
#include "src/devices/bin/driver_manager/fidl_txn.h"
#include "src/devices/bin/driver_manager/manifest_parser.h"
#include "src/devices/bin/driver_manager/package_resolver.h"
#include "src/devices/bin/driver_manager/unbind_task.h"
#include "src/devices/bin/driver_manager/vmo_writer.h"
#include "src/devices/lib/log/log.h"
namespace fio = fuchsia_io;
namespace {
namespace fdd = fuchsia_driver_development;
namespace fdm = fuchsia_device_manager;
namespace fdr = fuchsia_driver_registrar;
namespace fpm = fuchsia_power_manager;
constexpr char kDriverHostPath[] = "bin/driver_host";
constexpr char kBootFirmwarePath[] = "lib/firmware";
constexpr char kSystemPrefix[] = "/system/";
constexpr char kSystemFirmwarePath[] = "/system/lib/firmware";
constexpr const char* kItemsPath = fidl::DiscoverableProtocolDefaultPath<fuchsia_boot::Items>;
// The driver_host doesn't just define its own __asan_default_options()
// function because that conflicts with the build-system feature of injecting
// such a function based on the `asan_default_options` GN build argument.
// Since driver_host is only ever launched here, it can always get its
// necessary options through its environment variables. The sanitizer
// runtime combines the __asan_default_options() and environment settings.
constexpr char kAsanEnvironment[] =
"ASAN_OPTIONS="
// All drivers have a pure C ABI. But each individual driver might
// statically link in its own copy of some C++ library code. Since no
// C++ language relationships leak through the driver ABI, each driver is
// its own whole program from the perspective of the C++ language rules.
// But the ASan runtime doesn't understand this and wants to diagnose ODR
// violations when the same global is defined in multiple drivers, which
// is likely with C++ library use. There is no real way to teach the
// ASan instrumentation or runtime about symbol visibility and isolated
// worlds within the program, so the only thing to do is suppress the ODR
// violation detection. This unfortunately means real ODR violations
// within a single C++ driver won't be caught either.
"detect_odr_violation=0";
} // namespace
namespace statecontrol_fidl = fuchsia_hardware_power_statecontrol;
Coordinator::Coordinator(CoordinatorConfig config, InspectManager* inspect_manager,
async_dispatcher_t* dispatcher)
: config_(std::move(config)),
dispatcher_(dispatcher),
driver_loader_(config.boot_args, this),
suspend_handler_(this, config.suspend_fallback, config.suspend_timeout),
inspect_manager_(inspect_manager),
package_resolver_(config.boot_args) {
if (config_.oom_event) {
wait_on_oom_event_.set_object(config_.oom_event.get());
wait_on_oom_event_.set_trigger(ZX_EVENT_SIGNALED);
wait_on_oom_event_.Begin(dispatcher);
}
shutdown_system_state_ = config_.default_shutdown_system_state;
}
Coordinator::~Coordinator() {}
bool Coordinator::InSuspend() const { return suspend_handler().InSuspend(); }
bool Coordinator::InResume() const {
return (resume_context().flags() == ResumeContext::Flags::kResume);
}
zx_status_t Coordinator::RegisterWithPowerManager(fidl::ClientEnd<fio::Directory> devfs) {
auto system_state_endpoints = fidl::CreateEndpoints<fdm::SystemStateTransition>();
if (system_state_endpoints.is_error()) {
return system_state_endpoints.error_value();
}
std::unique_ptr<SystemStateManager> system_state_manager;
auto status = SystemStateManager::Create(
dispatcher_, this, std::move(system_state_endpoints->server), &system_state_manager);
if (status != ZX_OK) {
return status;
}
set_system_state_manager(std::move(system_state_manager));
auto result = service::Connect<fpm::DriverManagerRegistration>();
if (result.is_error()) {
LOGF(ERROR, "Failed to connect to fuchsia.power.manager: %s", result.status_string());
return result.error_value();
}
status = RegisterWithPowerManager(std::move(*result), std::move(system_state_endpoints->client),
std::move(devfs));
if (status == ZX_OK) {
set_power_manager_registered(true);
}
return ZX_OK;
}
zx_status_t Coordinator::RegisterWithPowerManager(
fidl::ClientEnd<fpm::DriverManagerRegistration> power_manager,
fidl::ClientEnd<fdm::SystemStateTransition> system_state_transition,
fidl::ClientEnd<fio::Directory> devfs) {
power_manager_client_.Bind(std::move(power_manager), dispatcher_);
auto result = power_manager_client_->Register(
std::move(system_state_transition), std::move(devfs),
[](fidl::WireResponse<fpm::DriverManagerRegistration::Register>* response) {
if (response->result.is_err()) {
fpm::wire::RegistrationError err = response->result.err();
if (err == fpm::wire::RegistrationError::kInvalidHandle) {
LOGF(ERROR, "Failed to register with power_manager.Invalid handle.\n");
return;
}
LOGF(ERROR, "Failed to register with power_manager\n");
return;
}
LOGF(INFO, "Registered with power manager successfully");
});
if (!result.ok()) {
LOGF(INFO, "Failed to register with power_manager: %d\n", result.status());
return result.status();
}
return ZX_OK;
}
zx_status_t Coordinator::InitCoreDevices(std::string_view sys_device_driver) {
root_device_ = fbl::MakeRefCounted<Device>(this, "root", fbl::String(), "root,", nullptr,
ZX_PROTOCOL_ROOT, zx::vmo(), zx::channel());
root_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND;
misc_device_ = fbl::MakeRefCounted<Device>(this, "misc", fbl::String(), "misc,", root_device_,
ZX_PROTOCOL_MISC_PARENT, zx::vmo(), zx::channel());
misc_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND;
sys_device_ = fbl::MakeRefCounted<Device>(this, "sys", sys_device_driver, "sys,", root_device_, 0,
zx::vmo(), zx::channel());
sys_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE;
test_device_ = fbl::MakeRefCounted<Device>(this, "test", fbl::String(), "test,", root_device_,
ZX_PROTOCOL_TEST_PARENT, zx::vmo(), zx::channel());
test_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND;
return ZX_OK;
}
const Driver* Coordinator::LibnameToDriver(std::string_view libname) const {
for (const auto& drv : drivers_) {
if (libname.compare(drv.libname) == 0) {
return &drv;
}
}
return driver_loader_.LibnameToDriver(libname);
}
zx_status_t Coordinator::LibnameToVmo(const fbl::String& libname, zx::vmo* out_vmo) const {
const Driver* drv = LibnameToDriver(libname);
if (drv == nullptr) {
LOGF(ERROR, "Cannot find driver '%s'", libname.data());
return ZX_ERR_NOT_FOUND;
}
// Check for cached DSO
if (drv->dso_vmo != ZX_HANDLE_INVALID) {
zx_status_t r = drv->dso_vmo.duplicate(
ZX_RIGHTS_BASIC | ZX_RIGHTS_PROPERTY | ZX_RIGHT_READ | ZX_RIGHT_EXECUTE | ZX_RIGHT_MAP,
out_vmo);
if (r != ZX_OK) {
LOGF(ERROR, "Cannot duplicate cached DSO for '%s' '%s'", drv->name.data(), libname.data());
}
return r;
} else {
return load_vmo(libname, out_vmo);
}
}
void Coordinator::DumpDevice(VmoWriter* vmo, const Device* dev, size_t indent) const {
zx_koid_t pid = dev->host() ? dev->host()->koid() : 0;
if (pid == 0) {
vmo->Printf("%*s[%s]\n", (int)(indent * 3), "", dev->name().data());
} else {
vmo->Printf("%*s%c%s%c pid=%zu %s\n", (int)(indent * 3), "",
dev->flags & DEV_CTX_PROXY ? '<' : '[', dev->name().data(),
dev->flags & DEV_CTX_PROXY ? '>' : ']', pid, dev->libname().data());
}
if (dev->proxy()) {
indent++;
DumpDevice(vmo, dev->proxy().get(), indent);
}
for (const auto& child : dev->children()) {
DumpDevice(vmo, &child, indent + 1);
}
}
void Coordinator::DumpState(VmoWriter* vmo) const {
DumpDevice(vmo, root_device_.get(), 0);
DumpDevice(vmo, misc_device_.get(), 1);
DumpDevice(vmo, sys_device_.get(), 1);
DumpDevice(vmo, test_device_.get(), 1);
}
void Coordinator::DumpDeviceProps(VmoWriter* vmo, const Device* dev) const {
if (dev->host()) {
vmo->Printf("Name [%s]%s%s%s\n", dev->name().data(), dev->libname().empty() ? "" : " Driver [",
dev->libname().empty() ? "" : dev->libname().data(),
dev->libname().empty() ? "" : "]");
vmo->Printf("Flags :%s%s%s%s%s%s\n", dev->flags & DEV_CTX_IMMORTAL ? " Immortal" : "",
dev->flags & DEV_CTX_MUST_ISOLATE ? " Isolate" : "",
dev->flags & DEV_CTX_MULTI_BIND ? " MultiBind" : "",
dev->flags & DEV_CTX_BOUND ? " Bound" : "",
(dev->state() == Device::State::kDead) ? " Dead" : "",
dev->flags & DEV_CTX_PROXY ? " Proxy" : "");
char a = (char)((dev->protocol_id() >> 24) & 0xFF);
char b = (char)((dev->protocol_id() >> 16) & 0xFF);
char c = (char)((dev->protocol_id() >> 8) & 0xFF);
char d = (char)(dev->protocol_id() & 0xFF);
vmo->Printf("ProtoId : '%c%c%c%c' %#08x(%u)\n", isprint(a) ? a : '.', isprint(b) ? b : '.',
isprint(c) ? c : '.', isprint(d) ? d : '.', dev->protocol_id(), dev->protocol_id());
const auto& props = dev->props();
vmo->Printf("%zu Propert%s\n", props.size(), props.size() == 1 ? "y" : "ies");
for (uint32_t i = 0; i < props.size(); ++i) {
const zx_device_prop_t* p = &props[i];
const char* param_name = di_bind_param_name(p->id);
if (param_name) {
vmo->Printf("[%2u/%2zu] : Value %#08x Id %s\n", i, props.size(), p->value, param_name);
} else {
vmo->Printf("[%2u/%2zu] : Value %#08x Id %#04hx\n", i, props.size(), p->value, p->id);
}
}
const auto& str_props = dev->str_props();
vmo->Printf("%zu String Propert%s\n", str_props.size(), str_props.size() == 1 ? "y" : "ies");
for (uint32_t i = 0; i < str_props.size(); ++i) {
const StrProperty* p = &str_props[i];
vmo->Printf("[%2u/%2zu] : %s=", i, str_props.size(), p->key.data());
std::visit(
[vmo](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, uint32_t>) {
vmo->Printf("%#08x\n", arg);
} else if constexpr (std::is_same_v<T, std::string>) {
vmo->Printf("\"%s\"\n", arg.data());
} else if constexpr (std::is_same_v<T, bool>) {
vmo->Printf("%s\n", arg ? "true" : "false");
} else {
vmo->Printf("(unknown value type!)\n");
}
},
p->value);
}
vmo->Printf("\n");
}
if (dev->proxy()) {
DumpDeviceProps(vmo, dev->proxy().get());
}
for (const auto& child : dev->children()) {
DumpDeviceProps(vmo, &child);
}
}
zx_handle_t get_service_root();
zx_status_t Coordinator::GetTopologicalPath(const fbl::RefPtr<const Device>& dev, char* out,
size_t max) const {
// TODO: Remove VLA.
char tmp[max];
char name_buf[fio::wire::kMaxFilename + strlen("dev/")];
char* path = tmp + max - 1;
*path = 0;
size_t total = 1;
fbl::RefPtr<const Device> itr = dev;
while (itr != nullptr) {
if (itr->flags & DEV_CTX_PROXY) {
itr = itr->parent();
}
const char* name;
if (&*itr == root_device_.get()) {
name = "dev";
} else if (itr->composite() != nullptr) {
strcpy(name_buf, "dev/");
strncpy(name_buf + strlen("dev/"), itr->name().data(), fio::wire::kMaxFilename);
name_buf[sizeof(name_buf) - 1] = 0;
name = name_buf;
} else {
name = itr->name().data();
}
size_t len = strlen(name) + 1;
if (len > (max - total)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(path - len + 1, name, len - 1);
path -= len;
*path = '/';
total += len;
itr = itr->parent();
}
memcpy(out, path, total);
return ZX_OK;
}
zx_status_t Coordinator::NewDriverHost(const char* name, fbl::RefPtr<DriverHost>* out) {
std::string binary = config_.path_prefix + kDriverHostPath;
std::string root_driver_path_arg;
std::vector<const char*> env;
if (config_.asan_drivers) {
// If there are any ASan drivers, use the ASan-supporting driver_host for
// all drivers because even a driver_host launched initially with just a
// non-ASan driver might later load an ASan driver. One day we might be
// able to be more flexible about which drivers must get loaded into the
// same driver_host and thus be able to use both ASan and non-ASan driver_hosts
// at the same time when only a subset of drivers use ASan.
//
// TODO(fxbug.dev/44814): The build logic to install the asan-ready driver_host
// under the alternate name is currently broken. So things only work
// if the build chose an asan-ready variant for the "main" driver_host.
// When this is restored in the build, this should select the right name.
// binary = kDriverHostAsanPath;
env.push_back(kAsanEnvironment);
}
auto driver_host_env = boot_args()->Collect("driver.");
if (!driver_host_env.ok()) {
return driver_host_env.status();
}
std::vector<std::string> strings;
for (auto& entry : driver_host_env->results) {
strings.emplace_back(entry.data(), entry.size());
}
// Make the clock backstop boot arg available to drivers that
// deal with time (RTC).
// TODO(fxbug.dev/60668): Remove once UTC time is removed from the kernel.
auto backstop_env = boot_args()->GetString("clock.backstop");
if (!backstop_env.ok()) {
return backstop_env.status();
}
auto backstop_env_value = std::move(backstop_env.value().value);
if (!backstop_env_value.is_null()) {
strings.push_back(std::string("clock.backstop=") +
std::string(backstop_env_value.data(), backstop_env_value.size()));
}
for (auto& entry : strings) {
env.push_back(entry.data());
}
if (config_.log_to_debuglog) {
env.push_back("devmgr.log-to-debuglog=true");
}
if (config_.verbose) {
env.push_back("devmgr.verbose=true");
}
root_driver_path_arg = "devmgr.root_driver_path=" + config_.path_prefix + "driver/";
env.push_back(root_driver_path_arg.c_str());
env.push_back(nullptr);
DriverHostConfig config{
.name = name,
.binary = binary.c_str(),
.env = env.data(),
.job = zx::unowned_job(config_.driver_host_job),
.root_resource = zx::unowned_resource(root_resource()),
.loader_service_connector = &loader_service_connector_,
.fs_provider = config_.fs_provider,
.coordinator = this,
};
fbl::RefPtr<DriverHost> dh;
zx_status_t status = DriverHost::Launch(config, &dh);
if (status != ZX_OK) {
return status;
}
launched_first_driver_host_ = true;
VLOGF(1, "New driver_host %p", dh.get());
*out = std::move(dh);
return ZX_OK;
}
// Add a new device to a parent device (same driver_host)
// New device is published in devfs.
// Caller closes handles on error, so we don't have to.
zx_status_t Coordinator::AddDevice(
const fbl::RefPtr<Device>& parent,
fidl::ClientEnd<fuchsia_device_manager::DeviceController> device_controller,
fidl::ServerEnd<fuchsia_device_manager::Coordinator> coordinator,
const fdm::wire::DeviceProperty* props_data, size_t props_count,
const fdm::wire::DeviceStrProperty* str_props_data, size_t str_props_count,
std::string_view name, uint32_t protocol_id, std::string_view driver_path,
std::string_view args, bool skip_autobind, bool has_init, bool always_init, zx::vmo inspect,
zx::channel client_remote, fbl::RefPtr<Device>* new_device) {
// If this is true, then |name_data|'s size is properly bounded.
static_assert(fdm::wire::kDeviceNameMax == ZX_DEVICE_NAME_MAX);
static_assert(fdm::wire::kPropertiesMax <= UINT32_MAX);
if (InSuspend()) {
LOGF(ERROR, "Add device '%.*s' forbidden in suspend", static_cast<int>(name.size()),
name.data());
return ZX_ERR_BAD_STATE;
}
if (InResume()) {
LOGF(ERROR, "Add device '%.*s' forbidden in resume", static_cast<int>(name.size()),
name.data());
return ZX_ERR_BAD_STATE;
}
if (parent->state() == Device::State::kUnbinding) {
LOGF(ERROR, "Add device '%.*s' forbidden while parent is unbinding",
static_cast<int>(name.size()), name.data());
return ZX_ERR_BAD_STATE;
}
fbl::Array<zx_device_prop_t> props(new zx_device_prop_t[props_count], props_count);
if (!props) {
return ZX_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < props_count; i++) {
props[i] = zx_device_prop_t{
.id = props_data[i].id,
.reserved = props_data[i].reserved,
.value = props_data[i].value,
};
}
fbl::Array<StrProperty> str_props(new StrProperty[str_props_count], str_props_count);
if (!str_props) {
return ZX_ERR_NO_MEMORY;
}
for (uint32_t i = 0; i < str_props_count; i++) {
str_props[i].key = str_props_data[i].key.get();
if (str_props_data[i].value.is_int_value()) {
str_props[i].value = str_props_data[i].value.int_value();
} else if (str_props_data[i].value.is_str_value()) {
str_props[i].value = std::string(str_props_data[i].value.str_value().get());
} else if (str_props_data[i].value.is_bool_value()) {
str_props[i].value = str_props_data[i].value.bool_value();
}
}
fbl::String name_str(name);
fbl::String driver_path_str(driver_path);
fbl::String args_str(args);
// TODO(fxbug.dev/43370): remove this check once init tasks can be enabled for all devices.
bool want_init_task = has_init || always_init;
fbl::RefPtr<Device> dev;
zx_status_t status =
Device::Create(this, parent, std::move(name_str), std::move(driver_path_str),
std::move(args_str), protocol_id, std::move(props), std::move(str_props),
std::move(coordinator), std::move(device_controller), want_init_task,
skip_autobind, std::move(inspect), std::move(client_remote), &dev);
if (status != ZX_OK) {
return status;
}
devices_.push_back(dev);
// Note that |dev->parent()| may not match |parent| here, so we should always
// use |dev->parent()|. This case can happen if |parent| refers to a device
// proxy.
// If we're creating a device that's using the fragment driver, inform the
// fragment.
if (fragment_driver_ != nullptr && dev->libname() == fragment_driver_->libname) {
for (auto& cur_fragment : dev->parent()->fragments()) {
if (cur_fragment.fragment_device() == nullptr) {
// Pick the first fragment that does not have a device added by the fragment
// driver.
cur_fragment.set_fragment_device(dev);
status = cur_fragment.composite()->TryAssemble();
if (status != ZX_OK && status != ZX_ERR_SHOULD_WAIT) {
LOGF(ERROR, "Failed to assemble composite device: %s", zx_status_get_string(status));
}
break;
}
}
}
VLOGF(1, "Added device %p '%s'", dev.get(), dev->name().data());
// TODO(fxbug.dev/43370): remove this once init tasks can be enabled for all devices.
if (!want_init_task) {
status = dev->SignalReadyForBind();
if (status != ZX_OK) {
return status;
}
VLOGF(1, "Published device %p '%s' args='%s' props=%zu parent=%p", dev.get(),
dev->name().data(), dev->args().data(), dev->props().size(), dev->parent().get());
}
*new_device = std::move(dev);
return ZX_OK;
}
zx_status_t Coordinator::MakeVisible(const fbl::RefPtr<Device>& dev) {
if (dev->state() == Device::State::kDead) {
return ZX_ERR_BAD_STATE;
}
if (dev->state() == Device::State::kInitializing) {
// This should only be called in response to the init hook completing.
return ZX_ERR_BAD_STATE;
}
if (dev->flags & DEV_CTX_INVISIBLE) {
dev->flags &= ~DEV_CTX_INVISIBLE;
devfs_advertise(dev);
zx_status_t r = dev->SignalReadyForBind();
if (r != ZX_OK) {
return r;
}
}
return ZX_OK;
}
void Coordinator::ScheduleRemove(const fbl::RefPtr<Device>& dev) {
dev->CreateUnbindRemoveTasks(
UnbindTaskOpts{.do_unbind = false, .post_on_create = true, .driver_host_requested = false});
}
void Coordinator::ScheduleDriverHostRequestedRemove(const fbl::RefPtr<Device>& dev,
bool do_unbind) {
dev->CreateUnbindRemoveTasks(UnbindTaskOpts{
.do_unbind = do_unbind, .post_on_create = true, .driver_host_requested = true});
}
void Coordinator::ScheduleDriverHostRequestedUnbindChildren(const fbl::RefPtr<Device>& parent) {
for (auto& child : parent->children()) {
child.CreateUnbindRemoveTasks(
UnbindTaskOpts{.do_unbind = true, .post_on_create = true, .driver_host_requested = true});
}
}
// Remove device from parent
// forced indicates this is removal due to a channel close
// or process exit, which means we should remove all other
// devices that share the driver_host at the same time
zx_status_t Coordinator::RemoveDevice(const fbl::RefPtr<Device>& dev, bool forced) {
if (forced && config_.crash_policy == DriverHostCrashPolicy::kRebootSystem) {
// TODO(fxbug.dev/67168): Trigger system restart more gracefully.
ZX_ASSERT(false);
}
dev->inc_num_removal_attempts();
if (dev->state() == Device::State::kDead) {
// This should not happen
LOGF(ERROR, "Cannot remove device %p '%s' twice", dev.get(), dev->name().data());
return ZX_ERR_BAD_STATE;
}
if (dev->flags & DEV_CTX_IMMORTAL) {
// This too should not happen
LOGF(ERROR, "Cannot remove device %p '%s' (immortal)", dev.get(), dev->name().data());
return ZX_ERR_BAD_STATE;
}
LOGF(INFO, "Removing device %p '%s' parent=%p", dev.get(), dev->name().data(),
dev->parent().get());
dev->set_state(Device::State::kDead);
// remove from devfs, preventing further OPEN attempts
devfs_unpublish(dev.get());
// Mark any suspend that's in-flight as completed, since if the device is
// removed it should be in its lowest state.
// TODO(teisenbe): Should we mark it as failed if this is a forced removal?
dev->CompleteSuspend(ZX_OK);
dev->CompleteInit(ZX_ERR_UNAVAILABLE);
fbl::RefPtr<DriverHost> dh = dev->host();
bool driver_host_dying = (dh != nullptr && (dh->flags() & DriverHost::Flags::kDying));
if (forced || driver_host_dying) {
// We are force removing all devices in the driver_host, so force complete any outstanding
// tasks.
dev->CompleteUnbind(ZX_ERR_UNAVAILABLE);
dev->CompleteRemove(ZX_ERR_UNAVAILABLE);
// If there is a device proxy, we need to create a new unbind task for it.
// For non-forced removals, the unbind task will handle scheduling the proxy removal.
if (dev->proxy()) {
ScheduleRemove(dev->proxy());
}
} else {
// We should not be removing a device while the unbind task is still running.
ZX_ASSERT(dev->GetActiveUnbind() == nullptr);
}
// Check if this device is a composite device, and if so disconnects from it
if (dev->composite()) {
dev->composite()->Remove();
}
// Check if this device is a composite fragment device
if (fragment_driver_ != nullptr && dev->libname() == fragment_driver_->libname) {
// If it is, then its parent will know about which one (since the parent
// is the actual device matched by the fragment description).
const auto& parent = dev->parent();
for (auto itr = parent->fragments().begin(); itr != parent->fragments().end();) {
auto& cur_fragment = *itr;
// Advance the iterator because we will erase the current element from the list.
++itr;
if (cur_fragment.fragment_device() == dev) {
cur_fragment.Unbind();
parent->fragments().erase(cur_fragment);
break;
}
}
}
// detach from driver_host
if (dh != nullptr) {
// We're holding on to a reference to the driver_host through |dh|.
// This is necessary to prevent it from being freed in the middle of
// the code below.
dev->set_host(nullptr);
// If we are responding to a disconnect,
// we'll remove all the other devices on this driver_host too.
// A side-effect of this is that the driver_host will be released,
// as well as any proxy devices.
if (forced) {
dh->flags() |= DriverHost::Flags::kDying;
fbl::RefPtr<Device> next;
fbl::RefPtr<Device> last;
while (!dh->devices().is_empty()) {
next = fbl::RefPtr(&dh->devices().front());
if (last == next) {
// This shouldn't be possible, but let's not infinite-loop if it happens
LOGF(FATAL, "Failed to remove device %p '%s' from driver_host", next.get(),
next->name().data());
}
RemoveDevice(next, false);
last = std::move(next);
}
// TODO: set a timer so if this driver_host does not finish dying
// in a reasonable amount of time, we fix the glitch.
}
dh.reset();
}
// if we have a parent, disconnect and downref it
fbl::RefPtr<Device> parent = dev->parent();
if (parent != nullptr) {
Device* real_parent;
if (parent->flags & DEV_CTX_PROXY) {
real_parent = parent->parent().get();
} else {
real_parent = parent.get();
}
dev->DetachFromParent();
if (!(dev->flags & DEV_CTX_PROXY)) {
if (parent->children().is_empty()) {
parent->flags &= (~DEV_CTX_BOUND);
if (real_parent->test_state() == Device::TestStateMachine::kTestUnbindSent) {
real_parent->test_event().signal(0, TEST_REMOVE_DONE_SIGNAL);
if (!(dev->flags & DEV_CTX_PROXY)) {
// remove from list of all devices
devices_.erase(*dev);
}
return ZX_OK;
}
// TODO: This code is to cause the bind process to
// restart and get a new driver_host to be launched
// when a driver_host dies. It should probably be
// more tied to driver_host teardown than it is.
// IF the policy is set such that we take action
// AND we are the last child of our parent
// AND our parent is not itself dead
// AND our parent is a BUSDEV
// AND our parent's driver_host is not dying
// THEN we will want to rebind our parent
if ((config_.crash_policy == DriverHostCrashPolicy::kRestartDriverHost) &&
(parent->state() != Device::State::kDead) && (parent->flags & DEV_CTX_MUST_ISOLATE) &&
((parent->host() == nullptr) ||
!(parent->host()->flags() & DriverHost::Flags::kDying))) {
VLOGF(1, "Bus device %p '%s' is unbound", parent.get(), parent->name().data());
if (parent->retries > 0) {
// Add device with an exponential backoff.
zx_status_t r = parent->SignalReadyForBind(parent->backoff);
if (r != ZX_OK) {
return r;
}
parent->backoff *= 2;
parent->retries--;
}
}
}
}
}
if (!(dev->flags & DEV_CTX_PROXY)) {
// remove from list of all devices
devices_.erase(*dev);
}
return ZX_OK;
}
zx_status_t Coordinator::AddCompositeDevice(const fbl::RefPtr<Device>& dev, std::string_view name,
fdm::wire::CompositeDeviceDescriptor comp_desc) {
std::unique_ptr<CompositeDevice> new_device;
zx_status_t status = CompositeDevice::Create(name, std::move(comp_desc), &new_device);
if (status != ZX_OK) {
return status;
}
// Try to bind the new composite device specification against existing
// devices.
for (auto& dev : devices_) {
if (!dev.is_bindable() && !dev.is_composite_bindable()) {
continue;
}
auto dev_ref = fbl::RefPtr(&dev);
size_t index;
if (new_device->TryMatchFragments(dev_ref, &index)) {
LOGF(INFO, "Device '%s' matched fragment %zu of composite '%s'", dev.name().data(), index,
new_device->name().data());
status = new_device->BindFragment(index, dev_ref);
if (status != ZX_OK) {
LOGF(ERROR, "Device '%s' failed to bind fragment %zu of composite '%s': %s",
dev.name().data(), index, new_device->name().data(), zx_status_get_string(status));
}
}
}
composite_devices_.push_back(std::move(new_device));
return ZX_OK;
}
static zx_status_t LoadFirmwareAt(int fd, const char* path, zx::vmo* vmo, size_t* size) {
fbl::unique_fd firmware_fd(openat(fd, path, O_RDONLY));
if (firmware_fd.get() < 0) {
if (errno != ENOENT) {
return ZX_ERR_IO;
}
return ZX_ERR_NOT_FOUND;
}
*size = lseek(firmware_fd.get(), 0, SEEK_END);
zx_status_t status = fdio_get_vmo_clone(firmware_fd.get(), vmo->reset_and_get_address());
return status;
}
zx_status_t Coordinator::LoadFirmware(const fbl::RefPtr<Device>& dev, const char* driver_libname,
const char* path, zx::vmo* vmo, size_t* size) {
const std::string fwdirs[] = {
config_.path_prefix + kBootFirmwarePath,
kSystemFirmwarePath,
};
// Must be a relative path and no funny business.
if (path[0] == '/' || path[0] == '.') {
return ZX_ERR_INVALID_ARGS;
}
// We are only going to check /system/ if the driver was loaded out of /system.
// This ensures that /system is available and loaded, as otherwise touching /system
// will wait, potentially forever.
size_t directories_to_check = 1;
if (strncmp(driver_libname, kSystemPrefix, std::size(kSystemPrefix) - 1) == 0) {
directories_to_check = std::size(fwdirs);
}
for (unsigned n = 0; n < directories_to_check; n++) {
fbl::unique_fd fd(open(fwdirs[n].c_str(), O_RDONLY, O_DIRECTORY));
if (fd.get() < 0) {
continue;
}
zx_status_t status = LoadFirmwareAt(fd.get(), path, vmo, size);
if (status == ZX_OK || status != ZX_ERR_NOT_FOUND) {
return status;
}
}
const Driver* driver = LibnameToDriver(driver_libname);
if (driver == nullptr || !driver->package_dir.is_valid()) {
return ZX_ERR_NOT_FOUND;
}
auto package_path = std::string("lib/firmware/") + path;
return LoadFirmwareAt(driver->package_dir.get(), package_path.c_str(), vmo, size);
}
// Returns true if the parent path is equal to or specifies a child device of the parent.
static bool path_is_child(const char* parent_path, const char* child_path) {
size_t parent_length = strlen(parent_path);
return (!strncmp(parent_path, child_path, parent_length) &&
(child_path[parent_length] == 0 || child_path[parent_length] == '/'));
}
zx_status_t Coordinator::GetMetadataRecurse(const fbl::RefPtr<Device>& dev, uint32_t type,
void* buffer, size_t buflen, size_t* size) {
// search dev and its parent devices for a match
fbl::RefPtr<Device> test = dev;
while (true) {
for (const auto& md : test->metadata()) {
if (md.type == type) {
if (buffer != nullptr) {
if (md.length > buflen) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buffer, md.Data(), md.length);
}
*size = md.length;
return ZX_OK;
}
}
if (test->parent() == nullptr) {
break;
}
test = test->parent();
}
// search fragments of composite devices
if (test->composite()) {
for (auto& fragment : test->composite()->bound_fragments()) {
auto dev = fragment.bound_device();
if (dev != nullptr) {
if (GetMetadataRecurse(dev, type, buffer, buflen, size) == ZX_OK) {
return ZX_OK;
}
}
}
}
return ZX_ERR_NOT_FOUND;
}
// Traverse up the device tree to find the metadata with the matching |type|.
// If not found, check the published metadata list for metadata with matching
// topological path.
// |buffer| can be nullptr, in which case only the size of the metadata is
// returned. This is used by GetMetadataSize method.
zx_status_t Coordinator::GetMetadata(const fbl::RefPtr<Device>& dev, uint32_t type, void* buffer,
size_t buflen, size_t* size) {
ZX_ASSERT(size != nullptr);
auto status = GetMetadataRecurse(dev, type, buffer, buflen, size);
if (status == ZX_OK) {
return ZX_OK;
}
// if no metadata is found, check list of metadata added via device_publish_metadata()
char path[fdm::wire::kDevicePathMax];
status = GetTopologicalPath(dev, path, sizeof(path));
if (status != ZX_OK) {
return status;
}
for (const auto& md : published_metadata_) {
const char* md_path = md.Data() + md.length;
if (md.type == type && path_is_child(md_path, path)) {
if (buffer != nullptr) {
if (md.length > buflen) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
memcpy(buffer, md.Data(), md.length);
}
*size = md.length;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t Coordinator::AddMetadata(const fbl::RefPtr<Device>& dev, uint32_t type,
const void* data, uint32_t length) {
std::unique_ptr<Metadata> md;
zx_status_t status = Metadata::Create(length, &md);
if (status != ZX_OK) {
return status;
}
md->type = type;
md->length = length;
memcpy(md->Data(), data, length);
dev->AddMetadata(std::move(md));
return ZX_OK;
}
zx_status_t Coordinator::PublishMetadata(const fbl::RefPtr<Device>& dev, const char* path,
uint32_t type, const void* data, uint32_t length) {
char caller_path[fdm::wire::kDevicePathMax];
zx_status_t status = GetTopologicalPath(dev, caller_path, sizeof(caller_path));
if (status != ZX_OK) {
return status;
}
// Check to see if the specified path is a child of the caller's path
if (path_is_child(caller_path, path)) {
// Caller is adding a path that matches itself or one of its children, which is allowed.
} else {
fbl::RefPtr<Device> itr = dev;
// Adding metadata to arbitrary paths is restricted to drivers running in the sys driver_host.
while (itr && itr != sys_device_) {
if (itr->proxy()) {
// this device is in a child driver_host
return ZX_ERR_ACCESS_DENIED;
}
itr = itr->parent();
}
if (!itr) {
return ZX_ERR_ACCESS_DENIED;
}
}
std::unique_ptr<Metadata> md;
status = Metadata::Create(length + strlen(path) + 1, &md);
if (status != ZX_OK) {
return status;
}
md->type = type;
md->length = length;
md->has_path = true;
memcpy(md->Data(), data, length);
strcpy(md->Data() + length, path);
published_metadata_.push_front(std::move(md));
return ZX_OK;
}
namespace {
// send message to driver_host, requesting the creation of a device
zx_status_t CreateDevice(const fbl::RefPtr<Device>& dev, fbl::RefPtr<DriverHost>& dh,
const char* args, zx::handle rpc_proxy) {
auto coordinator_endpoints = fidl::CreateEndpoints<fdm::Coordinator>();
if (coordinator_endpoints.is_error()) {
return coordinator_endpoints.error_value();
}
auto device_controller_request = dev->ConnectDeviceController(dev->coordinator->dispatcher());
if (dev->libname().size() != 0) {
zx::vmo vmo;
if (auto status = dev->coordinator->LibnameToVmo(dev->libname(), &vmo); status != ZX_OK) {
return status;
}
auto driver_path = fidl::StringView::FromExternal(dev->libname().data(), dev->libname().size());
auto args_view = fidl::StringView::FromExternal(args, strlen(args));
auto result = dh->controller()->CreateDevice(
std::move(coordinator_endpoints->client), std::move(device_controller_request), driver_path,
std::move(vmo), std::move(rpc_proxy), args_view, dev->local_id());
if (!result.ok()) {
return result.status();
}
} else {
auto result = dh->controller()->CreateDeviceStub(std::move(coordinator_endpoints->client),
std::move(device_controller_request),
dev->protocol_id(), dev->local_id());
if (!result.ok()) {
return result.status();
}
}
Device::Bind(dev, dev->coordinator->dispatcher(), std::move(coordinator_endpoints->server));
return ZX_OK;
}
// send message to driver_host, requesting the binding of a driver to a device
zx_status_t BindDriver(const fbl::RefPtr<Device>& dev, const char* libname) {
zx::vmo vmo;
zx_status_t status = dev->coordinator->LibnameToVmo(libname, &vmo);
if (status != ZX_OK) {
return status;
}
auto result = dev->device_controller()->BindDriver(
fidl::StringView::FromExternal(libname, strlen(libname)), std::move(vmo),
[dev](auto* response) {
if (response->status != ZX_OK) {
LOGF(ERROR, "Failed to bind driver '%s': %s", dev->name().data(),
zx_status_get_string(response->status));
return;
}
fbl::RefPtr<Device> real_parent;
if (dev->flags & DEV_CTX_PROXY) {
real_parent = dev->parent();
} else {
real_parent = dev;
}
for (auto& child : real_parent->children()) {
const char* drivername =
dev->coordinator->LibnameToDriver(child.libname().data())->name.data();
auto bootarg = fbl::StringPrintf("driver.%s.compatibility-tests-enable", drivername);
auto compat_test_enabled = dev->coordinator->boot_args()->GetBool(
fidl::StringView::FromExternal(bootarg), false);
if (compat_test_enabled.ok() && compat_test_enabled->value &&
(real_parent->test_state() == Device::TestStateMachine::kTestNotStarted)) {
bootarg = fbl::StringPrintf("driver.%s.compatibility-tests-wait-time", drivername);
auto test_wait_time =
dev->coordinator->boot_args()->GetString(fidl::StringView::FromExternal(bootarg));
zx::duration test_time = kDefaultTestTimeout;
if (test_wait_time.ok() && !test_wait_time->value.is_null()) {
auto test_timeout =
std::string{test_wait_time->value.data(), test_wait_time->value.size()};
test_time = zx::msec(atoi(test_timeout.data()));
}
real_parent->DriverCompatibilityTest(test_time, std::nullopt);
break;
} else if (real_parent->test_state() == Device::TestStateMachine::kTestBindSent) {
real_parent->test_event().signal(0, TEST_BIND_DONE_SIGNAL);
break;
}
}
if (response->test_output.is_valid()) {
LOGF(INFO, "Setting test channel for driver '%s'", dev->name().data());
auto status = dev->set_test_output(std::move(response->test_output),
dev->coordinator->dispatcher());
if (status != ZX_OK) {
LOGF(ERROR, "Failed to wait on test output for driver '%s': %s", dev->name().data(),
zx_status_get_string(status));
}
}
});
if (!result.ok()) {
return result.status();
}
dev->flags |= DEV_CTX_BOUND;
return ZX_OK;
}
} // namespace
// Create the proxy node for the given device if it doesn't exist and ensure it
// has a driver_host. If |target_driver_host| is not nullptr and the proxy doesn't have
// a driver_host yet, |target_driver_host| will be used for it. Otherwise a new driver_host
// will be created.
zx_status_t Coordinator::PrepareProxy(const fbl::RefPtr<Device>& dev,
fbl::RefPtr<DriverHost> target_driver_host) {
ZX_ASSERT(!(dev->flags & DEV_CTX_PROXY) && (dev->flags & DEV_CTX_MUST_ISOLATE));
// proxy args are "processname,args"
const char* arg0 = dev->args().data();
const char* arg1 = strchr(arg0, ',');
if (arg1 == nullptr) {
LOGF(ERROR, "Missing proxy arguments, expected '%s,args' (see fxbug.dev/33674)", arg0);
return ZX_ERR_INTERNAL;
}
size_t arg0len = arg1 - arg0;
arg1++;
char driver_hostname[32];
snprintf(driver_hostname, sizeof(driver_hostname), "driver_host:%.*s", (int)arg0len, arg0);
zx_status_t r;
if (dev->proxy() == nullptr && (r = dev->CreateProxy()) != ZX_OK) {
LOGF(ERROR, "Cannot create proxy device '%s': %s", dev->name().data(), zx_status_get_string(r));
return r;
}
// if this device has no driver_host, first instantiate it
if (dev->proxy()->host() == nullptr) {
zx::channel h0, h1;
// the immortal root devices do not provide proxy rpc
bool need_proxy_rpc = !(dev->flags & DEV_CTX_IMMORTAL);
if (need_proxy_rpc || dev == sys_device_) {
// create rpc channel for proxy device to talk to the busdev it proxys
if ((r = zx::channel::create(0, &h0, &h1)) < 0) {
return r;
}
}
if (target_driver_host == nullptr) {
if ((r = NewDriverHost(driver_hostname, &target_driver_host)) < 0) {
LOGF(ERROR, "Failed to create driver_host '%s': %s", driver_hostname,
zx_status_get_string(r));
return r;
}
}
dev->proxy()->set_host(std::move(target_driver_host));
if ((r = CreateDevice(dev->proxy(), dev->proxy()->host(), arg1, std::move(h1))) < 0) {
LOGF(ERROR, "Failed to create proxy device '%s' in driver_host '%s': %s", dev->name().data(),
driver_hostname, zx_status_get_string(r));
return r;
}
if (need_proxy_rpc) {
if (auto result = dev->device_controller()->ConnectProxy(std::move(h0)); !result.ok()) {
LOGF(ERROR, "Failed to connect to proxy device '%s' in driver_host '%s': %s",
dev->name().data(), driver_hostname, zx_status_get_string(result.status()));
}
}
if (dev == sys_device_) {
if ((r = fdio_service_connect(kItemsPath, h0.release())) != ZX_OK) {
LOGF(ERROR, "Failed to connect to %s: %s", kItemsPath, zx_status_get_string(r));
}
}
zx::channel client_remote = dev->take_client_remote();
if (client_remote.is_valid()) {
if ((r = devfs_connect(dev->proxy().get(),
fidl::ServerEnd<fio::Node>(std::move(client_remote)))) != ZX_OK) {
LOGF(ERROR, "Failed to connect to service from proxy device '%s' in driver_host '%s': %s",
dev->name().data(), driver_hostname, zx_status_get_string(r));
}
}
}
return ZX_OK;
}
zx_status_t Coordinator::AttemptBind(const Driver* drv, const fbl::RefPtr<Device>& dev) {
// cannot bind driver to already bound device
if ((dev->flags & DEV_CTX_BOUND) &&
!(dev->flags & (DEV_CTX_MULTI_BIND | DEV_CTX_ALLOW_MULTI_COMPOSITE))) {
return ZX_ERR_BAD_STATE;
}
if (!(dev->flags & DEV_CTX_MUST_ISOLATE)) {
// non-busdev is pretty simple
if (dev->host() == nullptr) {
LOGF(ERROR, "Cannot bind to device '%s', it has no driver_host", dev->name().data());
return ZX_ERR_BAD_STATE;
}
return ::BindDriver(dev, drv->libname.c_str());
}
zx_status_t r;
if ((r = PrepareProxy(dev, nullptr /* target_driver_host */)) < 0) {
return r;
}
r = ::BindDriver(dev->proxy(), drv->libname.c_str());
// TODO(swetland): arrange to mark us unbound when the proxy (or its driver_host) goes away
if ((r == ZX_OK) && !(dev->flags & DEV_CTX_MULTI_BIND)) {
dev->flags |= DEV_CTX_BOUND;
}
return r;
}
void Coordinator::HandleNewDevice(const fbl::RefPtr<Device>& dev) {
// If the device has a proxy, we actually want to wait for the proxy device to be
// created and connect to that.
if (!(dev->flags & DEV_CTX_MUST_ISOLATE)) {
zx::channel client_remote = dev->take_client_remote();
if (client_remote.is_valid()) {
zx_status_t status =
devfs_connect(dev.get(), fidl::ServerEnd<fio::Node>(std::move(client_remote)));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to connect to service from proxy device '%s': %s", dev->name().data(),
zx_status_get_string(status));
}
}
}
// TODO(tesienbe): We probably should do something with the return value
// from this...
BindDevice(dev, {} /* libdrvname */, true /* new device */);
}
void Coordinator::Suspend(uint32_t flags, SuspendCallback callback) {
// TODO(ravoorir) : Change later to queue the suspend when resume is in progress.
// Similarly, when Suspend is in progress, resume should be queued. When a resume is
// in queue, and another suspend request comes in, we should nullify the resume that
// is in queue.
if (InResume()) {
LOGF(ERROR, "Aborting system-suspend, a system resume is in progresss");
if (callback) {
callback(ZX_ERR_UNAVAILABLE);
}
return;
}
suspend_handler_.Suspend(flags, std::move(callback));
}
void Coordinator::Resume(ResumeContext ctx, std::function<void(zx_status_t)> callback) {
if (!sys_device_->proxy()) {
return;
}
if (InSuspend()) {
return;
}
auto schedule_resume = [this, callback](fbl::RefPtr<Device> dev) {
auto completion = [this, dev, callback](zx_status_t status) {
dev->clear_active_resume();
auto& ctx = resume_context();
if (status != ZX_OK) {
LOGF(ERROR, "Failed to resume: %s", zx_status_get_string(status));
ctx.set_flags(ResumeContext::Flags::kSuspended);
auto task = ctx.take_pending_task(*dev);
callback(status);
return;
}
std::optional<fbl::RefPtr<ResumeTask>> task = ctx.take_pending_task(*dev);
if (task.has_value()) {
ctx.push_completed_task(std::move(task.value()));
} else {
// Something went wrong
LOGF(ERROR, "Failed to resume, cannot find matching pending task");
callback(ZX_ERR_INTERNAL);
return;
}
if (ctx.pending_tasks_is_empty()) {
async::PostTask(dispatcher_, [this, callback] {
resume_context().reset_completed_tasks();
callback(ZX_OK);
});
}
};
auto task = ResumeTask::Create(dev, static_cast<uint32_t>(resume_context().target_state()),
std::move(completion));
resume_context().push_pending_task(task);
dev->SetActiveResume(std::move(task));
};
resume_context() = std::move(ctx);
for (auto& dev : devices_) {
schedule_resume(fbl::RefPtr(&dev));
if (dev.proxy()) {
schedule_resume(dev.proxy());
}
}
schedule_resume(sys_device_);
schedule_resume(sys_device_->proxy());
// Post a delayed task in case drivers do not complete the resume.
auto status = async::PostDelayedTask(
dispatcher_,
[this, callback] {
if (!InResume()) {
return;
}
LOGF(ERROR, "System resume timed out");
callback(ZX_ERR_TIMED_OUT);
// TODO(ravoorir): Figure out what is the best strategy
// of for recovery here. Should we put back all devices
// in suspend? In future, this could be more interactive
// with the UI.
},
config_.resume_timeout);
if (status != ZX_OK) {
LOGF(ERROR, "Failure to create resume timeout watchdog");
}
}
void Coordinator::Resume(SystemPowerState target_state, ResumeCallback callback) {
Resume(ResumeContext(ResumeContext::Flags::kResume, target_state), std::move(callback));
}
std::unique_ptr<Driver> Coordinator::ValidateDriver(std::unique_ptr<Driver> drv) {
if ((drv->flags & ZIRCON_DRIVER_NOTE_FLAG_ASAN) && !config_.asan_drivers) {
if (launched_first_driver_host_) {
LOGF(ERROR, "%s (%s) requires ASan, cannot load after boot; use devmgr.devhost.asan=true",
drv->libname.data(), drv->name.data());
return nullptr;
}
config_.asan_drivers = true;
}
return drv;
}
// DriverAdded is called when a driver is added after the
// devcoordinator has started. The driver is added to the new-drivers
// list and work is queued to process it.
void Coordinator::DriverAdded(Driver* drv, const char* version) {
fbl::DoublyLinkedList<std::unique_ptr<Driver>> driver_list;
driver_list.push_back(std::unique_ptr<Driver>(drv));
async::PostTask(dispatcher_, [this, driver_list = std::move(driver_list)]() mutable {
AddAndBindDrivers(std::move(driver_list));
});
}
// DriverAddedInit is called from driver enumeration during
// startup and before the devcoordinator starts running. Enumerated
// drivers are added directly to the all-drivers or fallback list.
//
// TODO: fancier priorities
void Coordinator::DriverAddedInit(Driver* drv, const char* version) {
auto driver = ValidateDriver(std::unique_ptr<Driver>(drv));
if (!driver) {
return;
}
// Record the special fragment driver when we see it
if (driver->libname.data() == GetFragmentDriverPath()) {
fragment_driver_ = driver.get();
}
if (driver->fallback) {
// TODO(fxbug.dev/44586): remove this once a better solution for driver prioritization is
// implemented.
for (auto& name : config_.eager_fallback_drivers) {
if (driver->name == name) {
LOGF(INFO, "Marking fallback driver '%s' as eager.", driver->name.c_str());
driver->fallback = false;
break;
}
}
}
if (driver->fallback) {
// fallback driver, load only if all else fails
fallback_drivers_.push_front(std::move(driver));
} else if (version[0] == '!') {
// debugging / development hack
// prioritize drivers with version "!..." over others
drivers_.push_front(std::move(driver));
} else {
drivers_.push_back(std::move(driver));
}
}
zx_status_t Coordinator::BindDriverToDevice(const fbl::RefPtr<Device>& dev, const Driver* drv,
bool autobind, const AttemptBindFunc& attempt_bind) {
zx_status_t status = MatchDeviceToDriver(dev, drv, autobind);
if (status != ZX_OK) {
return status;
}
status = attempt_bind(drv, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind driver '%s' to device '%s': %s", drv->name.data(),
dev->name().data(), zx_status_get_string(status));
}
if (status == ZX_ERR_NEXT) {
// Convert ERR_NEXT to avoid confusing the caller
status = ZX_ERR_INTERNAL;
}
return status;
}
// BindDriver is called when a new driver becomes available to
// the Coordinator. Existing devices are inspected to see if the
// new driver is bindable to them (unless they are already bound).
zx_status_t Coordinator::BindDriver(Driver* drv, const AttemptBindFunc& attempt_bind) {
zx_status_t status = BindDriverToDevice(root_device_, drv, true /* autobind */, attempt_bind);
if (status != ZX_ERR_NEXT) {
return status;
}
status = BindDriverToDevice(misc_device_, drv, true /* autobind */, attempt_bind);
if (status != ZX_ERR_NEXT) {
return status;
}
status = BindDriverToDevice(test_device_, drv, true /* autobind */, attempt_bind);
if (status != ZX_ERR_NEXT) {
return status;
}
if (!running_) {
return ZX_ERR_UNAVAILABLE;
}
for (auto& dev : devices_) {
zx_status_t status =
BindDriverToDevice(fbl::RefPtr(&dev), drv, true /* autobind */, attempt_bind);
if (status == ZX_ERR_NEXT || status == ZX_ERR_ALREADY_BOUND) {
continue;
}
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t Coordinator::MatchDeviceToDriver(const fbl::RefPtr<Device>& dev, const Driver* driver,
bool autobind) {
if ((dev->flags & DEV_CTX_BOUND) && !(dev->flags & DEV_CTX_ALLOW_MULTI_COMPOSITE) &&
!(dev->flags & DEV_CTX_MULTI_BIND)) {
return ZX_ERR_ALREADY_BOUND;
}
if (autobind && dev->should_skip_autobind()) {
return ZX_ERR_NEXT;
}
if (!dev->is_bindable() && !(dev->is_composite_bindable())) {
return ZX_ERR_NEXT;
}
if (!driver_is_bindable(driver, dev->protocol_id(), dev->props(), dev->str_props(), autobind)) {
return ZX_ERR_NEXT;
}
return ZX_OK;
}
void Coordinator::BindAllDevicesDriverIndex() {
zx_status_t status = MatchAndBindDeviceDriverIndex(root_device_);
if (status != ZX_OK && status != ZX_ERR_NEXT) {
LOGF(ERROR, "DriverIndex failed to match root_device: %d", status);
return;
}
status = MatchAndBindDeviceDriverIndex(misc_device_);
if (status != ZX_OK && status != ZX_ERR_NEXT) {
LOGF(ERROR, "DriverIndex failed to match misc_device: %d", status);
return;
}
status = MatchAndBindDeviceDriverIndex(test_device_);
if (status != ZX_OK && status != ZX_ERR_NEXT) {
LOGF(ERROR, "DriverIndex failed to match test_device: %d", status);
return;
}
for (auto& dev : devices_) {
auto dev_ref = fbl::RefPtr(&dev);
zx_status_t status = MatchAndBindDeviceDriverIndex(dev_ref);
if (status == ZX_ERR_NEXT || status == ZX_ERR_ALREADY_BOUND) {
continue;
}
if (status != ZX_OK) {
return;
}
}
}
void Coordinator::ScheduleBaseDriverLoading() {
auto result = config_.driver_index->WaitForBaseDrivers(
[this](fidl::WireResponse<fdf::DriverIndex::WaitForBaseDrivers>* response) {
BindAllDevicesDriverIndex();
});
if (!result.ok()) {
// TODO(dgilhooley): Change this back to an ERROR once DriverIndex is included in the build.
LOGF(INFO, "Failed to connect to DriverIndex: %d", result.status());
}
}
zx_status_t Coordinator::MatchAndBindDeviceDriverIndex(const fbl::RefPtr<Device>& dev) {
auto drivers = driver_loader_.MatchDeviceDriverIndex(dev);
for (auto driver : drivers) {
zx_status_t status = AttemptBind(driver, dev);
if (status != ZX_OK && status != ZX_ERR_NEXT && status != ZX_ERR_ALREADY_BOUND) {
LOGF(ERROR, "Failed to bind driver '%s' to device '%s': %s", driver->libname.data(),
dev->name().data(), zx_status_get_string(status));
}
}
return ZX_OK;
}
zx::status<std::vector<const Driver*>> Coordinator::MatchDevice(const fbl::RefPtr<Device>& dev,
std::string_view drvlibname) {
// shouldn't be possible to get a bind request for a proxy device
if (dev->flags & DEV_CTX_PROXY) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
std::vector<const Driver*> matched_drivers;
// A libname of "" means a general rebind request
// instead of a specific request
bool autobind = drvlibname.size() == 0;
for (const Driver& driver : drivers_) {
if (!autobind && drvlibname.compare(driver.libname)) {
continue;
}
zx_status_t status = MatchDeviceToDriver(dev, &driver, autobind);
if (status == ZX_ERR_ALREADY_BOUND) {
return zx::error(ZX_ERR_ALREADY_BOUND);
}
if (status == ZX_ERR_NEXT) {
continue;
}
if (status == ZX_OK) {
matched_drivers.push_back(&driver);
}
// If the device doesn't support multibind (this is a devmgr-internal setting),
// then return on first match or failure.
// Otherwise, keep checking all the drivers.
if (!(dev->flags & DEV_CTX_MULTI_BIND)) {
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(matched_drivers));
}
}
// Check the Driver Index for a driver.
{
auto drivers = driver_loader_.MatchDeviceDriverIndex(dev, drvlibname);
for (auto driver : drivers) {
matched_drivers.push_back(driver);
}
}
return zx::ok(std::move(matched_drivers));
}
zx_status_t Coordinator::BindDevice(const fbl::RefPtr<Device>& dev, std::string_view drvlibname,
bool new_device) {
// shouldn't be possible to get a bind request for a proxy device
if (dev->flags & DEV_CTX_PROXY) {
return ZX_ERR_NOT_SUPPORTED;
}
// A libname of "" means a general rebind request
// instead of a specific request
bool autobind = drvlibname.size() == 0;
// Attempt composite device matching first. This is unnecessary if a
// specific driver has been requested.
if (autobind) {
zx_status_t status;
for (auto& composite : composite_devices_) {
size_t index;
if (composite.TryMatchFragments(dev, &index)) {
LOGF(INFO, "Device '%s' matched fragment %zu of composite '%s'", dev->name().data(), index,
composite.name().data());
status = composite.BindFragment(index, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Device '%s' failed to bind fragment %zu of composite '%s': %s",
dev->name().data(), index, composite.name().data(), zx_status_get_string(status));
return status;
}
}
}
}
// TODO: disallow if we're in the middle of enumeration, etc
zx::status<std::vector<const Driver*>> result = MatchDevice(dev, drvlibname);
if (!result.is_ok()) {
return result.error_value();
}
auto drivers = std::move(result.value());
for (const Driver* driver : drivers) {
zx_status_t status = AttemptBind(driver, dev);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind driver '%s' to device '%s': %s", driver->name.data(),
dev->name().data(), zx_status_get_string(status));
}
}
// Notify observers that this device is available again
// Needed for non-auto-binding drivers like GPT against block, etc
if (!new_device && autobind) {
devfs_advertise_modified(dev);
}
return ZX_OK;
}
void Coordinator::AddAndBindDrivers(fbl::DoublyLinkedList<std::unique_ptr<Driver>> drivers) {
std::unique_ptr<Driver> driver;
while ((driver = drivers.pop_front()) != nullptr) {
driver = ValidateDriver(std::move(driver));
if (!driver) {
continue;
}
Driver* driver_ptr = driver.get();
drivers_.push_back(std::move(driver));
zx_status_t status = BindDriver(driver_ptr);
if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) {
LOGF(ERROR, "Failed to bind driver '%s': %s", driver_ptr->name.data(),
zx_status_get_string(status));
}
}
}
void Coordinator::StartLoadingNonBootDrivers() { driver_loader_.StartSystemLoadingThread(); }
void Coordinator::BindFallbackDrivers() {
for (auto& driver : fallback_drivers_) {
LOGF(INFO, "Fallback driver '%s' is available", driver.name.data());
}
AddAndBindDrivers(std::move(fallback_drivers_));
}
void Coordinator::BindDrivers() { AddAndBindDrivers(std::move(drivers_)); }
// TODO(fxbug.dev/42257): Temporary helper to convert state to flags.
// Will be removed eventually.
uint32_t Coordinator::GetSuspendFlagsFromSystemPowerState(
statecontrol_fidl::wire::SystemPowerState state) {
switch (state) {
case statecontrol_fidl::wire::SystemPowerState::kFullyOn:
return 0;
case statecontrol_fidl::wire::SystemPowerState::kReboot:
return statecontrol_fidl::wire::kSuspendFlagReboot;
case statecontrol_fidl::wire::SystemPowerState::kRebootBootloader:
return statecontrol_fidl::wire::kSuspendFlagRebootBootloader;
case statecontrol_fidl::wire::SystemPowerState::kRebootRecovery:
return statecontrol_fidl::wire::kSuspendFlagRebootRecovery;
case statecontrol_fidl::wire::SystemPowerState::kPoweroff:
return statecontrol_fidl::wire::kSuspendFlagPoweroff;
case statecontrol_fidl::wire::SystemPowerState::kMexec:
return statecontrol_fidl::wire::kSuspendFlagMexec;
case statecontrol_fidl::wire::SystemPowerState::kSuspendRam:
return statecontrol_fidl::wire::kSuspendFlagSuspendRam;
default:
return 0;
}
}
zx::status<std::vector<fdd::wire::DriverInfo>> Coordinator::GetDriverInfo(
fidl::AnyAllocator& allocator, const std::vector<const Driver*>& drivers) {
std::vector<fdd::wire::DriverInfo> driver_info_vec;
// TODO(fxbug.dev/80033): Support base drivers.
for (const auto& driver : drivers) {
fdd::wire::DriverInfo driver_info(allocator);
driver_info.set_name(allocator,
fidl::StringView(allocator, {driver->name.data(), driver->name.size()}));
driver_info.set_url(
allocator, fidl::StringView(allocator, {driver->libname.data(), driver->libname.size()}));
if (driver->bytecode_version == 1) {
auto* binding = std::get_if<std::unique_ptr<zx_bind_inst_t[]>>(&driver->binding);
if (!binding) {
return zx::error(ZX_ERR_NOT_FOUND);
}
auto binding_insts = binding->get();
uint32_t count = 0;
if (driver->binding_size > 0) {
count = driver->binding_size / sizeof(binding_insts[0]);
}
if (count > fdm::wire::kBindRulesInstructionsMax) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
using fdm::wire::BindInstruction;
fidl::VectorView<BindInstruction> instructions(allocator, count);
for (uint32_t i = 0; i < count; i++) {
instructions[i] = BindInstruction{
.op = binding_insts[i].op,
.arg = binding_insts[i].arg,
.debug = binding_insts[i].debug,
};
}
driver_info.set_bind_rules(
allocator, fdd::wire::BindRulesBytecode::WithBytecodeV1(allocator, instructions));
} else if (driver->bytecode_version == 2) {
auto* binding = std::get_if<std::unique_ptr<uint8_t[]>>(&driver->binding);
if (!binding) {
return zx::error(ZX_ERR_NOT_FOUND);
}
fidl::VectorView<uint8_t> bytecode(allocator, driver->binding_size);
for (uint32_t i = 0; i < driver->binding_size; i++) {
bytecode[i] = binding->get()[i];
}
driver_info.set_bind_rules(allocator,
fdd::wire::BindRulesBytecode::WithBytecodeV2(allocator, bytecode));
} else {
return zx::error(ZX_ERR_INVALID_ARGS);
}
driver_info_vec.push_back(std::move(driver_info));
}
return zx::ok(std::move(driver_info_vec));
}
void Coordinator::GetDriverInfo(GetDriverInfoRequestView request,
GetDriverInfoCompleter::Sync& completer) {
std::vector<const Driver*> driver_list;
if (request->driver_filter.empty()) {
for (const auto& driver : drivers()) {
driver_list.push_back(&driver);
}
} else {
for (const auto& d : request->driver_filter) {
std::string_view driver_path(d.data(), d.size());
const Driver* driver = LibnameToDriver(driver_path);
if (driver == nullptr) {
completer.ReplyError(ZX_ERR_NOT_FOUND);
return;
}
driver_list.push_back(driver);
}
}
fidl::FidlAllocator allocator;
auto result = GetDriverInfo(allocator, driver_list);
if (result.is_error()) {
completer.ReplyError(result.status_value());
} else {
completer.ReplySuccess(fidl::VectorView<fdd::wire::DriverInfo>::FromExternal(*result));
}
}
void Coordinator::Register(RegisterRequestView request, RegisterCompleter::Sync& completer) {
std::string driver_url_str(request->package_url.url.data(), request->package_url.url.size());
zx_status_t status = LoadEphemeralDriver(&package_resolver_, driver_url_str);
if (status != ZX_OK) {
LOGF(ERROR, "Could not load '%s'", driver_url_str.c_str());
completer.ReplyError(status);
return;
}
LOGF(INFO, "Loaded driver '%s'", driver_url_str.c_str());
completer.ReplySuccess();
}
zx_status_t Coordinator::LoadEphemeralDriver(internal::PackageResolverInterface* resolver,
const std::string& package_url) {
ZX_ASSERT(config_.enable_ephemeral);
auto result = resolver->FetchDriver(package_url);
if (!result.is_ok()) {
return result.status_value();
}
fbl::DoublyLinkedList<std::unique_ptr<Driver>> driver_list;
driver_list.push_back(std::move(result.value()));
async::PostTask(dispatcher_, [this, driver_list = std::move(driver_list)]() mutable {
AddAndBindDrivers(std::move(driver_list));
});
return ZX_OK;
}
zx::status<std::vector<fdd::wire::DeviceInfo>> Coordinator::GetDeviceInfo(
fidl::AnyAllocator& allocator, const std::vector<fbl::RefPtr<Device>>& devices) {
std::vector<fdd::wire::DeviceInfo> device_info_vec;
for (const auto& device : devices) {
if (device->props().size() > fdm::wire::kPropertiesMax) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
if (device->str_props().size() > fdm::wire::kPropertiesMax) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
fdd::wire::DeviceInfo device_info(allocator);
// id leaks internal pointers, but since this is a development only API, it shouldn't be
// a big deal.
device_info.set_id(allocator, reinterpret_cast<uint64_t>(device.get()));
// TODO(fxbug.dev/80094): Handle multiple parents case.
fidl::VectorView<uint64_t> parent_ids(allocator, 1);
parent_ids[0] = reinterpret_cast<uint64_t>(device->parent().get());
device_info.set_parent_ids(allocator, parent_ids);
size_t child_count = 0;
for (const auto& child __attribute__((unused)) : device->children()) {
child_count++;
}
if (child_count > 0) {
fidl::VectorView<uint64_t> child_ids(allocator, child_count);
size_t i = 0;
for (const auto& child : device->children()) {
child_ids[i++] = reinterpret_cast<uint64_t>(&child);
}
device_info.set_child_ids(allocator, child_ids);
}
if (device->host()) {
device_info.set_driver_host_koid(allocator, device->host()->koid());
}
char path[fdm::wire::kDevicePathMax + 1];
if (auto status = GetTopologicalPath(device, path, sizeof(path)); status != ZX_OK) {
return zx::error(status);
}
device_info.set_topological_path(allocator, fidl::StringView(allocator, {path, strlen(path)}));
device_info.set_bound_driver_libname(
allocator,
fidl::StringView(allocator, {device->libname().data(), device->libname().size()}));
fidl::VectorView<fdm::wire::DeviceProperty> props(allocator, device->props().size());
for (size_t i = 0; i < device->props().size(); i++) {
const auto& prop = device->props()[i];
props[i] = fdm::wire::DeviceProperty{
.id = prop.id,
.reserved = prop.reserved,
.value = prop.value,
};
}
fidl::VectorView<fdm::wire::DeviceStrProperty> str_props(allocator, device->str_props().size());
for (size_t i = 0; i < device->str_props().size(); i++) {
const auto& str_prop = device->str_props()[i];
if (str_prop.value.valueless_by_exception()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
auto fidl_str_prop = fdm::wire::DeviceStrProperty{
.key = fidl::StringView(allocator, str_prop.key),
};
if (std::holds_alternative<uint32_t>(str_prop.value)) {
auto* prop_val = std::get_if<uint32_t>(&str_prop.value);
fidl_str_prop.value = fdm::wire::PropertyValue::WithIntValue(allocator, *prop_val);
} else if (std::holds_alternative<std::string>(str_prop.value)) {
auto* prop_val = std::get_if<std::string>(&str_prop.value);
fidl_str_prop.value = fdm::wire::PropertyValue::WithStrValue(
allocator, fidl::StringView(allocator, *prop_val));
} else if (std::holds_alternative<bool>(str_prop.value)) {
auto* prop_val = std::get_if<bool>(&str_prop.value);
fidl_str_prop.value = fdm::wire::PropertyValue::WithBoolValue(allocator, *prop_val);
}
str_props[i] = fidl_str_prop;
}
device_info.set_property_list(allocator, fdm::wire::DevicePropertyList{
.props = props,
.str_props = str_props,
});
device_info.set_flags(allocator, device->flags);
device_info_vec.push_back(std::move(device_info));
}
return zx::ok(std::move(device_info_vec));
}
void Coordinator::GetDeviceInfo(GetDeviceInfoRequestView request,
GetDeviceInfoCompleter::Sync& completer) {
std::vector<fbl::RefPtr<Device>> device_list;
if (request->device_filter.empty()) {
for (auto& device : devices()) {
device_list.push_back(fbl::RefPtr(&device));
}
} else {
for (const auto& device_path : request->device_filter) {
fbl::RefPtr<Device> device;
zx_status_t status = devfs_walk(root_device_->devnode(), device_path.data(), &device);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
device_list.push_back(std::move(device));
}
}
fidl::FidlAllocator allocator;
auto result = GetDeviceInfo(allocator, device_list);
if (result.is_error()) {
completer.ReplyError(result.status_value());
} else {
completer.ReplySuccess(fidl::VectorView<fdd::wire::DeviceInfo>::FromExternal(*result));
}
}
void Coordinator::Suspend(SuspendRequestView request, SuspendCompleter::Sync& completer) {
Suspend(request->flags, [completer = completer.ToAsync()](zx_status_t status) mutable {
completer.Reply(status);
});
}
void Coordinator::UnregisterSystemStorageForShutdown(
UnregisterSystemStorageForShutdownRequestView request,
UnregisterSystemStorageForShutdownCompleter::Sync& completer) {
suspend_handler().UnregisterSystemStorageForShutdown(
[completer = completer.ToAsync()](zx_status_t status) mutable { completer.Reply(status); });
}
void Coordinator::DumpTree(DumpTreeRequestView request, DumpTreeCompleter::Sync& completer) {
VmoWriter writer{std::move(request->output)};
DumpState(&writer);
completer.Reply(writer.status(), writer.written(), writer.available());
}
void Coordinator::DumpDrivers(DumpDriversRequestView request,
DumpDriversCompleter::Sync& completer) {
VmoWriter writer{std::move(request->output)};
bool first = true;
for (const auto& drv : drivers_) {
writer.Printf("%sName : %s\n", first ? "" : "\n", drv.name.c_str());
writer.Printf("Driver : %s\n", !drv.libname.empty() ? drv.libname.c_str() : "(null)");
writer.Printf("Flags : %#08x\n", drv.flags);
writer.Printf("Bytecode Version : %u\n", drv.bytecode_version);
if (!drv.binding_size) {
continue;
}
if (drv.bytecode_version == 1) {
auto* binding = std::get_if<std::unique_ptr<zx_bind_inst_t[]>>(&drv.binding);
if (!binding) {
continue;
}
char line[256];
uint32_t count = drv.binding_size / static_cast<uint32_t>(sizeof(binding->get()[0]));
writer.Printf("Binding : %u instruction%s (%u bytes)\n", count, (count == 1) ? "" : "s",
drv.binding_size);
for (uint32_t i = 0; i < count; ++i) {
di_dump_bind_inst(&binding->get()[i], line, sizeof(line));
writer.Printf("[%u/%u]: %s\n", i + 1, count, line);
}
} else if (drv.bytecode_version == 2) {
auto* binding = std::get_if<std::unique_ptr<uint8_t[]>>(&drv.binding);
if (!binding) {
continue;
}
writer.Printf("Bytecode (%u byte%s): \n", drv.binding_size,
(drv.binding_size == 1) ? "" : "s");
for (uint32_t i = 0; i < drv.binding_size; ++i) {
writer.Printf("0x%02x", (binding->get()[i]));
}
writer.Printf("\n");
}
first = false;
}
completer.Reply(writer.status(), writer.written(), writer.available());
}
void Coordinator::DumpBindingProperties(DumpBindingPropertiesRequestView request,
DumpBindingPropertiesCompleter::Sync& completer) {
VmoWriter writer{std::move(request->output)};
DumpDeviceProps(&writer, root_device_.get());
DumpDeviceProps(&writer, misc_device_.get());
DumpDeviceProps(&writer, sys_device_.get());
DumpDeviceProps(&writer, test_device_.get());
completer.Reply(writer.status(), writer.written(), writer.available());
}
zx_status_t Coordinator::InitOutgoingServices(const fbl::RefPtr<fs::PseudoDir>& svc_dir) {
static_assert(fdm::wire::kSuspendFlagReboot == DEVICE_SUSPEND_FLAG_REBOOT);
static_assert(fdm::wire::kSuspendFlagPoweroff == DEVICE_SUSPEND_FLAG_POWEROFF);
const auto admin = [this](fidl::ServerEnd<fdm::Administrator> request) {
fidl::BindServer<fidl::WireServer<fdm::Administrator>>(dispatcher_, std::move(request), this);
return ZX_OK;
};
zx_status_t status = svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdm::Administrator>,
fbl::MakeRefCounted<fs::Service>(admin));
if (status != ZX_OK) {
return status;
}
const auto system_state_manager_register =
[this](fidl::ServerEnd<fdm::SystemStateTransition> request) {
auto status = fidl::BindSingleInFlightOnly<fidl::WireServer<fdm::SystemStateTransition>>(
dispatcher_, std::move(request), std::make_unique<SystemStateManager>(this));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind to client channel for '%s': %s",
fidl::DiscoverableProtocolName<fdm::SystemStateTransition>,
zx_status_get_string(status));
}
return status;
};
status = svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdm::SystemStateTransition>,
fbl::MakeRefCounted<fs::Service>(system_state_manager_register));
if (status != ZX_OK) {
LOGF(ERROR, "Failed to add entry in service directory for '%s': %s",
fidl::DiscoverableProtocolName<fdm::SystemStateTransition>, zx_status_get_string(status));
return status;
}
const auto driver_dev = [this](fidl::ServerEnd<fdd::DriverDevelopment> request) {
auto status = fidl::BindSingleInFlightOnly<fidl::WireServer<fdd::DriverDevelopment>>(
dispatcher_, std::move(request), this);
if (status != ZX_OK) {
LOGF(ERROR, "Failed to bind to client channel for '%s': %s",
fidl::DiscoverableProtocolName<fdd::DriverDevelopment>, zx_status_get_string(status));
}
return status;
};
status = svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdd::DriverDevelopment>,
fbl::MakeRefCounted<fs::Service>(driver_dev));
if (status != ZX_OK) {
return status;
}
if (config_.enable_ephemeral) {
const auto driver_registrar = [this](fidl::ServerEnd<fdr::DriverRegistrar> request) {
driver_registrar_binding_ = fidl::BindServer<fidl::WireServer<fdr::DriverRegistrar>>(
dispatcher_, std::move(request), this);
return ZX_OK;
};
status = svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdr::DriverRegistrar>,
fbl::MakeRefCounted<fs::Service>(driver_registrar));
if (status != ZX_OK) {
return status;
}
}
const auto debug = [this](fidl::ServerEnd<fdm::DebugDumper> request) {
fidl::BindServer<fidl::WireServer<fdm::DebugDumper>>(dispatcher_, std::move(request), this);
return ZX_OK;
};
return svc_dir->AddEntry(fidl::DiscoverableProtocolName<fdm::DebugDumper>,
fbl::MakeRefCounted<fs::Service>(debug));
}
void Coordinator::OnOOMEvent(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
suspend_handler_.ShutdownFilesystems([](zx_status_t status) {});
}
std::string Coordinator::GetFragmentDriverPath() const {
return config_.path_prefix + "driver/fragment.so";
}
void Coordinator::RestartDriverHosts(RestartDriverHostsRequestView request,
RestartDriverHostsCompleter::Sync& completer) {
std::string_view driver_path(request->driver_path.data(), request->driver_path.size());
// Find devices containing the driver.
for (auto& dev : devices_) {
// Call remove on the device's driver host if it contains the driver.
if (dev.libname().compare(driver_path) == 0) {
LOGF(INFO, "Device %s found in restart driver hosts.", dev.name().data());
LOGF(INFO, "Shutting down host: %ld.", dev.host()->koid());
// Unbind and Remove all the devices in the Driver Host.
ScheduleUnbindRemoveAllDevices(dev.host());
}
}
completer.ReplySuccess();
}
void Coordinator::ScheduleUnbindRemoveAllDevices(const fbl::RefPtr<DriverHost> driver_host) {
for (auto& dev : driver_host->devices()) {
// This will also call on all the children of the device.
dev.CreateUnbindRemoveTasks(
UnbindTaskOpts{.do_unbind = true, .post_on_create = true, .driver_host_requested = false});
}
}