| // 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 "coordinator.h" |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <threads.h> |
| |
| #include <ddk/driver.h> |
| #include <driver-info/driver-info.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/unique_ptr.h> |
| #include <fuchsia/boot/c/fidl.h> |
| #include <fuchsia/io/c/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/receiver.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fidl-async/bind.h> |
| #include <lib/fidl/coding.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fzl/owned-vmo-mapper.h> |
| #include <lib/zircon-internal/ktrace.h> |
| #include <lib/zx/job.h> |
| #include <libzbi/zbi-cpp.h> |
| #include <zircon/assert.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/policy.h> |
| #include <zircon/syscalls/system.h> |
| |
| #include <utility> |
| |
| #include "../shared/env.h" |
| #include "../shared/fdio.h" |
| #include "../shared/fidl_txn.h" |
| #include "../shared/log.h" |
| #include "composite-device.h" |
| #include "devfs.h" |
| #include "devhost-loader-service.h" |
| #include "fidl.h" |
| #include "vmo-writer.h" |
| |
| namespace { |
| |
| // Handle ID to use for the root job when spawning devhosts. This number must |
| // match the value used in system/dev/misc/sysinfo/sysinfo.c. |
| constexpr uint32_t kIdHJobRoot = 4; |
| |
| constexpr char kBootFirmwarePath[] = "/boot/lib/firmware"; |
| constexpr char kSystemFirmwarePath[] = "/system/lib/firmware"; |
| constexpr char kItemsPath[] = "/bootsvc/" fuchsia_boot_Items_Name; |
| constexpr char kRootJobPath[] = "/bootsvc/" fuchsia_boot_RootJob_Name; |
| |
| // Tells VFS to exit by shutting down the fshost. |
| void vfs_exit(const zx::event& fshost_event) { |
| zx_status_t status; |
| if ((status = fshost_event.signal(0, FSHOST_SIGNAL_EXIT)) != ZX_OK) { |
| printf("devcoordinator: Failed to signal VFS exit\n"); |
| return; |
| } |
| if ((status = fshost_event.wait_one(FSHOST_SIGNAL_EXIT_DONE, zx::deadline_after(zx::sec(60)), |
| nullptr)) != ZX_OK) { |
| printf("devcoordinator: Failed to wait for VFS exit completion\n"); |
| return; |
| } |
| |
| printf("devcoordinator: Successfully waited for VFS exit completion\n"); |
| } |
| |
| void suspend_fallback(const zx::resource& root_resource, uint32_t flags) { |
| log(INFO, "devcoordinator: suspend fallback with flags 0x%08x\n", flags); |
| if (flags == DEVICE_SUSPEND_FLAG_REBOOT) { |
| zx_system_powerctl(root_resource.get(), ZX_SYSTEM_POWERCTL_REBOOT, nullptr); |
| } else if (flags == DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER) { |
| zx_system_powerctl(root_resource.get(), ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER, nullptr); |
| } else if (flags == DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY) { |
| zx_system_powerctl(root_resource.get(), ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY, nullptr); |
| } else if (flags == DEVICE_SUSPEND_FLAG_POWEROFF) { |
| zx_system_powerctl(root_resource.get(), ZX_SYSTEM_POWERCTL_SHUTDOWN, nullptr); |
| } |
| } |
| |
| } // namespace |
| |
| namespace devmgr { |
| |
| const char* kComponentDriverPath = "/boot/driver/component.so"; |
| |
| uint32_t log_flags = LOG_ERROR | LOG_INFO; |
| |
| Coordinator::Coordinator(CoordinatorConfig config) |
| : config_(std::move(config)), outgoing_services_(config_.dispatcher) { |
| InitOutgoingServices(); |
| } |
| |
| Coordinator::~Coordinator() { |
| drivers_.clear(); |
| } |
| |
| bool Coordinator::InSuspend() const { |
| return suspend_context().flags() == SuspendContext::Flags::kSuspend; |
| } |
| |
| zx_status_t Coordinator::InitializeCoreDevices(const char* sys_device_driver) { |
| root_device_ = fbl::MakeRefCounted<Device>(this, "root", fbl::String(), "root,", nullptr, |
| ZX_PROTOCOL_ROOT, 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::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::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::channel()); |
| test_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND; |
| return ZX_OK; |
| } |
| |
| const Driver* Coordinator::LibnameToDriver(const fbl::String& libname) const { |
| for (const auto& drv : drivers_) { |
| if (libname == drv.libname) { |
| return &drv; |
| } |
| } |
| return nullptr; |
| } |
| |
| static zx_status_t load_vmo(const fbl::String& libname, zx::vmo* out_vmo) { |
| int fd = open(libname.data(), O_RDONLY); |
| if (fd < 0) { |
| log(ERROR, "devcoordinator: cannot open driver '%s'\n", libname.data()); |
| return ZX_ERR_IO; |
| } |
| zx::vmo nonexec_vmo; |
| zx::vmo vmo; |
| zx_status_t r = fdio_get_vmo_clone(fd, nonexec_vmo.reset_and_get_address()); |
| close(fd); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoordinator: cannot get driver vmo '%s'\n", libname.data()); |
| return r; |
| } |
| r = nonexec_vmo.replace_as_executable(zx::handle(), &vmo); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoordinator: cannot mark driver vmo exec '%s'\n", libname.data()); |
| return r; |
| } |
| const char* vmo_name = strrchr(libname.data(), '/'); |
| if (vmo_name != nullptr) { |
| ++vmo_name; |
| } else { |
| vmo_name = libname.data(); |
| } |
| vmo.set_property(ZX_PROP_NAME, vmo_name, strlen(vmo_name)); |
| *out_vmo = std::move(vmo); |
| return r; |
| } |
| |
| zx_status_t Coordinator::LibnameToVmo(const fbl::String& libname, zx::vmo* out_vmo) const { |
| const Driver* drv = LibnameToDriver(libname); |
| if (drv == nullptr) { |
| log(ERROR, "devcoordinator: cannot find driver '%s'\n", 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) { |
| log(ERROR, "devcoordinator: cannot duplicate cached dso for '%s' '%s'\n", |
| 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; |
| char extra[256]; |
| if (log_flags & LOG_DEVLC) { |
| snprintf(extra, sizeof(extra), " dev=%p ", dev); |
| } else { |
| extra[0] = 0; |
| } |
| if (pid == 0) { |
| vmo->Printf("%*s[%s]%s\n", (int)(indent * 3), "", dev->name().data(), extra); |
| } else { |
| vmo->Printf("%*s%c%s%c pid=%zu%s %s\n", (int)(indent * 3), "", |
| dev->flags & DEV_CTX_PROXY ? '<' : '[', dev->name().data(), |
| dev->flags & DEV_CTX_PROXY ? '>' : ']', pid, extra, 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->flags & DEV_CTX_DEAD ? " 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' 0x%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 0x%08x Id %s\n", i, props.size(), p->value, |
| param_name); |
| } else { |
| vmo->Printf("[%2u/%2zu] : Value 0x%08x Id 0x%04hx\n", i, props.size(), p->value, |
| p->id); |
| } |
| } |
| vmo->Printf("\n"); |
| } |
| |
| if (dev->proxy()) { |
| DumpDeviceProps(vmo, dev->proxy().get()); |
| } |
| for (const auto& child : dev->children()) { |
| DumpDeviceProps(vmo, &child); |
| } |
| } |
| |
| void Coordinator::DumpGlobalDeviceProps(VmoWriter* vmo) const { |
| DumpDeviceProps(vmo, root_device_.get()); |
| DumpDeviceProps(vmo, misc_device_.get()); |
| DumpDeviceProps(vmo, sys_device_.get()); |
| DumpDeviceProps(vmo, test_device_.get()); |
| } |
| |
| void Coordinator::DumpDrivers(VmoWriter* vmo) const { |
| bool first = true; |
| for (const auto& drv : drivers_) { |
| vmo->Printf("%sName : %s\n", first ? "" : "\n", drv.name.c_str()); |
| vmo->Printf("Driver : %s\n", !drv.libname.empty() ? drv.libname.c_str() : "(null)"); |
| vmo->Printf("Flags : 0x%08x\n", drv.flags); |
| if (drv.binding_size) { |
| char line[256]; |
| uint32_t count = drv.binding_size / static_cast<uint32_t>(sizeof(drv.binding[0])); |
| vmo->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(&drv.binding[i], line, sizeof(line)); |
| vmo->Printf("[%u/%u]: %s\n", i + 1, count, line); |
| } |
| } |
| first = false; |
| } |
| } |
| |
| static const char* get_devhost_bin(bool asan_drivers) { |
| // If there are any ASan drivers, use the ASan-supporting devhost for |
| // all drivers because even a devhost 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 devhost and thus be able to use both ASan and non-ASan |
| // devhosts at the same time when only a subset of drivers use ASan. |
| if (asan_drivers) |
| return "/boot/bin/devhost.asan"; |
| return "/boot/bin/devhost"; |
| } |
| |
| 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[fuchsia_io_MAX_FILENAME + 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(), fuchsia_io_MAX_FILENAME); |
| 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; |
| } |
| |
| static zx_status_t dc_launch_devhost(Devhost* host, DevhostLoaderService* loader_service, |
| const char* devhost_bin, const char* name, |
| const char* const* env, zx_handle_t hrpc, |
| const zx::resource& root_resource, |
| zx::unowned_job devhost_job) { |
| zx::channel loader_connection; |
| if (loader_service != nullptr) { |
| zx_status_t status = loader_service->Connect(&loader_connection); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to use loader service: %s\n", |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| // Give devhosts the root resource if we have it (in tests, we may not) |
| // TODO: limit root resource to root devhost only |
| zx::resource resource; |
| if (root_resource.is_valid()) { |
| zx_status_t status = root_resource.duplicate(ZX_RIGHT_SAME_RIGHTS, &resource); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to duplicate root resource: %d\n", status); |
| } |
| } |
| |
| // Give devhosts access to fuchsia.boot.RootJob, in order to implement the |
| // sysinfo driver. This should eventually be removed once we have a better |
| // way of passing around the services within sysinfo. |
| zx::channel root_job_svc, root_job_remote; |
| zx_status_t status = zx::channel::create(0, &root_job_svc, &root_job_remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = fdio_service_connect(kRootJobPath, root_job_remote.release()); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to connect to root job service: %d\n", status); |
| return status; |
| } |
| |
| constexpr size_t kMaxActions = 6; |
| fdio_spawn_action_t actions[kMaxActions]; |
| size_t actions_count = 0; |
| actions[actions_count++] = |
| fdio_spawn_action_t{.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = name}}; |
| // TODO: constrain to /svc/device |
| actions[actions_count++] = fdio_spawn_action_t{ |
| .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| .ns = {.prefix = "/svc", .handle = fs_clone("svc").release()}, |
| }; |
| actions[actions_count++] = fdio_spawn_action_t{ |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_USER0, 0), .handle = hrpc}, |
| }; |
| actions[actions_count++] = fdio_spawn_action_t{ |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_USER0, kIdHJobRoot), .handle = root_job_svc.release()}, |
| }; |
| if (resource.is_valid()) { |
| actions[actions_count++] = fdio_spawn_action_t{ |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_RESOURCE, 0), .handle = resource.release()}, |
| }; |
| } |
| |
| uint32_t spawn_flags = FDIO_SPAWN_CLONE_ENVIRON; |
| if (loader_connection.is_valid()) { |
| actions[actions_count++] = fdio_spawn_action_t{ |
| .action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_LDSVC_LOADER, 0), .handle = loader_connection.release()}, |
| }; |
| } else { |
| spawn_flags |= FDIO_SPAWN_DEFAULT_LDSVC; |
| } |
| ZX_ASSERT(actions_count <= kMaxActions); |
| |
| zx::process proc; |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| // Inherit devmgr's environment (including kernel cmdline) |
| const char* const argv[] = { |
| devhost_bin, |
| nullptr, |
| }; |
| status = fdio_spawn_etc(devhost_job->get(), spawn_flags, argv[0], argv, env, actions_count, |
| actions, proc.reset_and_get_address(), err_msg); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: launch devhost '%s': failed: %s: %s\n", name, |
| zx_status_get_string(status), err_msg); |
| return status; |
| } |
| |
| host->set_proc(std::move(proc)); |
| |
| zx_info_handle_basic_t info; |
| if (host->proc()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr) == |
| ZX_OK) { |
| host->set_koid(info.koid); |
| } |
| log(INFO, "devcoordinator: launch devhost '%s': pid=%zu\n", name, host->koid()); |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::NewDevhost(const char* name, Devhost* parent, Devhost** out) { |
| auto dh = std::make_unique<Devhost>(); |
| if (dh == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // TODO: Use zx::channel here. |
| zx_handle_t hrpc, dh_hrpc; |
| zx_status_t status = zx_channel_create(0, &hrpc, &dh_hrpc); |
| if (status != ZX_OK) { |
| return status; |
| } |
| dh->set_hrpc(dh_hrpc); |
| |
| fbl::Vector<const char*> env; |
| boot_args().Collect("driver.", &env); |
| env.push_back(nullptr); |
| status = |
| dc_launch_devhost(dh.get(), loader_service_, get_devhost_bin(config_.asan_drivers), name, |
| env.get(), hrpc, root_resource(), zx::unowned_job(config_.devhost_job)); |
| if (status != ZX_OK) { |
| zx_handle_close(dh->hrpc()); |
| return status; |
| } |
| launched_first_devhost_ = true; |
| |
| if (parent) { |
| dh->set_parent(parent); |
| dh->parent()->AddRef(); |
| dh->parent()->children().push_back(dh.get()); |
| } |
| devhosts_.push_back(dh.get()); |
| |
| log(DEVLC, "devcoordinator: new host %p\n", dh.get()); |
| |
| *out = dh.release(); |
| return ZX_OK; |
| } |
| |
| void Coordinator::ReleaseDevhost(Devhost* dh) { |
| if (!dh->Release()) { |
| return; |
| } |
| log(INFO, "devcoordinator: destroy host %p\n", dh); |
| Devhost* parent = dh->parent(); |
| if (parent != nullptr) { |
| dh->parent()->children().erase(*dh); |
| dh->set_parent(nullptr); |
| ReleaseDevhost(parent); |
| } |
| devhosts_.erase(*dh); |
| zx_handle_close(dh->hrpc()); |
| dh->proc()->kill(); |
| delete dh; |
| } |
| |
| // Add a new device to a parent device (same devhost) |
| // 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, zx::channel rpc, |
| const uint64_t* props_data, size_t props_count, |
| fbl::StringPiece name, uint32_t protocol_id, |
| fbl::StringPiece driver_path, fbl::StringPiece args, |
| bool invisible, zx::channel client_remote, |
| fbl::RefPtr<Device>* new_device) { |
| // If this is true, then |name_data|'s size is properly bounded. |
| static_assert(fuchsia_device_manager_DEVICE_NAME_MAX == ZX_DEVICE_NAME_MAX); |
| static_assert(fuchsia_device_manager_PROPERTIES_MAX <= UINT32_MAX); |
| |
| if (InSuspend()) { |
| log(ERROR, "devcoordinator: rpc: add-device '%.*s' forbidden in suspend\n", |
| static_cast<int>(name.size()), name.data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| log(RPC_IN, "devcoordinator: rpc: add-device '%.*s' args='%.*s'\n", |
| static_cast<int>(name.size()), name.data(), static_cast<int>(args.size()), args.data()); |
| |
| fbl::Array<zx_device_prop_t> props(new zx_device_prop_t[props_count], props_count); |
| if (!props) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| static_assert(sizeof(zx_device_prop_t) == sizeof(props_data[0])); |
| memcpy(props.get(), props_data, props_count * sizeof(zx_device_prop_t)); |
| |
| fbl::AllocChecker ac; |
| fbl::String name_str(name, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| fbl::String driver_path_str(driver_path, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| fbl::String args_str(args, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| 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(rpc), invisible, 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 component driver, inform the |
| // component. |
| if (component_driver_ != nullptr && dev->libname() == component_driver_->libname) { |
| CompositeDeviceComponent* component = dev->parent()->component(); |
| component->set_component_device(dev); |
| status = component->composite()->TryAssemble(); |
| if (status != ZX_OK && status != ZX_ERR_SHOULD_WAIT) { |
| log(ERROR, "devcoordinator: failed to assemble composite: %s\n", |
| zx_status_get_string(status)); |
| } |
| } |
| |
| if (!invisible) { |
| log(DEVLC, "devcoord: publish %p '%s' props=%zu args='%s' parent=%p\n", dev.get(), |
| dev->name().data(), dev->props().size(), dev->args().data(), dev->parent().get()); |
| status = dev->SignalReadyForBind(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| *new_device = std::move(dev); |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::MakeVisible(const fbl::RefPtr<Device>& dev) { |
| if (dev->flags & DEV_CTX_DEAD) { |
| 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; |
| } |
| |
| // 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 devhost at the same time |
| zx_status_t Coordinator::RemoveDevice(const fbl::RefPtr<Device>& dev, bool forced) { |
| if (dev->flags & DEV_CTX_DEAD) { |
| // This should not happen |
| log(ERROR, "devcoordinator: cannot remove dev %p name='%s' twice!\n", dev.get(), |
| dev->name().data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (dev->flags & DEV_CTX_IMMORTAL) { |
| // This too should not happen |
| log(ERROR, "devcoordinator: cannot remove dev %p name='%s' (immortal)\n", dev.get(), |
| dev->name().data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| log(DEVLC, "devcoordinator: remove %p name='%s' parent=%p\n", dev.get(), dev->name().data(), |
| dev->parent().get()); |
| dev->flags |= DEV_CTX_DEAD; |
| |
| // 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); |
| |
| if (dev->proxy()) { |
| zx_status_t r = dh_send_remove_device(dev->proxy().get()); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to send message in dc_remove_device: %d\n", r); |
| } |
| } |
| |
| // 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 component device |
| if (component_driver_ != nullptr && dev->libname() == component_driver_->libname) { |
| // If it is, then its parent will know about which one (since the parent |
| // is the actual device matched by the component description). |
| const auto& parent = dev->parent(); |
| parent->component()->Unbind(); |
| } |
| |
| // detach from devhost |
| Devhost* dh = dev->host(); |
| if (dh != nullptr) { |
| dev->host()->devices().erase(*dev); |
| // Acquire an extra reference to the devhost that gets released below. |
| // This is necessary to prevent a dh from being freed in the middle of |
| // the code below. |
| dh->AddRef(); |
| dev->set_host(nullptr); |
| |
| // If we are responding to a disconnect, |
| // we'll remove all the other devices on this devhost too. |
| // A side-effect of this is that the devhost will be released, |
| // as well as any proxy devices. |
| if (forced) { |
| dh->flags() |= Devhost::Flags::kDying; |
| |
| fbl::RefPtr<Device> next; |
| fbl::RefPtr<Device> last; |
| while (!dh->devices().is_empty()) { |
| next = fbl::WrapRefPtr(&dh->devices().front()); |
| if (last == next) { |
| // This shouldn't be possible, but let's not infinite-loop if it happens |
| log(ERROR, "devcoordinator: fatal: failed to remove dev %p from devhost\n", |
| next.get()); |
| abort(); |
| } |
| RemoveDevice(next, false); |
| last = std::move(next); |
| } |
| |
| // TODO: set a timer so if this devhost does not finish dying |
| // in a reasonable amount of time, we fix the glitch. |
| } |
| |
| ReleaseDevhost(dh); |
| } |
| |
| // if we have a parent, disconnect and downref it |
| fbl::RefPtr<Device> parent = dev->parent(); |
| if (parent != nullptr) { |
| dev->DetachFromParent(); |
| if (!(dev->flags & DEV_CTX_PROXY)) { |
| if (parent->children().is_empty()) { |
| parent->flags &= (~DEV_CTX_BOUND); |
| |
| // TODO: This code is to cause the bind process to |
| // restart and get a new devhost to be launched |
| // when a devhost dies. It should probably be |
| // more tied to devhost teardown than it is. |
| |
| if (parent->test_state() == Device::TestStateMachine::kTestUnbindSent) { |
| 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; |
| } |
| // IF 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 devhost is not dying |
| // THEN we will want to rebind our parent |
| if (!(parent->flags & DEV_CTX_DEAD) && (parent->flags & DEV_CTX_MUST_ISOLATE) && |
| ((parent->host() == nullptr) || |
| !(parent->host()->flags() & Devhost::Flags::kDying))) { |
| |
| log(DEVLC, "devcoordinator: bus device %p name='%s' is unbound\n", 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, fbl::StringPiece name, |
| const zx_device_prop_t* props_data, size_t props_count, |
| const fuchsia_device_manager_DeviceComponent* components, |
| size_t components_count, uint32_t coresident_device_index) { |
| // Only the platform bus driver should be able to use this. It is the |
| // descendant of the sys device node. |
| if (dev->parent() != sys_device_) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| std::unique_ptr<CompositeDevice> new_device; |
| zx_status_t status = |
| CompositeDevice::Create(name, props_data, props_count, components, components_count, |
| coresident_device_index, &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()) { |
| continue; |
| } |
| |
| auto dev_ref = fbl::WrapRefPtr(&dev); |
| size_t index; |
| if (new_device->TryMatchComponents(dev_ref, &index)) { |
| log(SPEW, "devcoordinator: dev='%s' matched component %zu of composite='%s'\n", |
| dev.name().data(), index, new_device->name().data()); |
| status = new_device->BindComponent(index, dev_ref); |
| if (status != ZX_OK) { |
| log(ERROR, |
| "devcoordinator: dev='%s' failed to bind component %zu of composite='%s': %s\n", |
| dev.name().data(), index, new_device->name().data(), |
| zx_status_get_string(status)); |
| } |
| } |
| } |
| |
| composite_devices_.push_back(std::move(new_device)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::LoadFirmware(const fbl::RefPtr<Device>& dev, const char* path, |
| zx::vmo* vmo, size_t* size) { |
| static const char* fwdirs[] = { |
| kBootFirmwarePath, |
| kSystemFirmwarePath, |
| }; |
| |
| // Must be a relative path and no funny business. |
| if (path[0] == '/' || path[0] == '.') { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| int fd, fwfd; |
| for (unsigned n = 0; n < fbl::count_of(fwdirs); n++) { |
| if ((fd = open(fwdirs[n], O_RDONLY, O_DIRECTORY)) < 0) { |
| continue; |
| } |
| fwfd = openat(fd, path, O_RDONLY); |
| close(fd); |
| if (fwfd >= 0) { |
| *size = lseek(fwfd, 0, SEEK_END); |
| zx::vmo nonexec_vmo; |
| zx_status_t r = fdio_get_vmo_clone(fwfd, nonexec_vmo.reset_and_get_address()); |
| if (r == ZX_OK) { |
| r = zx_vmo_replace_as_executable(nonexec_vmo.release(), ZX_HANDLE_INVALID, |
| vmo->reset_and_get_address()); |
| } |
| close(fwfd); |
| return r; |
| } |
| if (errno != ENOENT) { |
| return ZX_ERR_IO; |
| } |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // 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 components of composite devices |
| if (test->composite()) { |
| for (auto& component : test->composite()->bound_components()) { |
| auto dev = component.bound_device(); |
| if (dev != nullptr) { |
| if (GetMetadataRecurse(dev, type, buffer, buflen, size) == ZX_OK) { |
| return ZX_OK; |
| } |
| } |
| } |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t Coordinator::GetMetadata(const fbl::RefPtr<Device>& dev, uint32_t type, void* buffer, |
| size_t buflen, size_t* size) { |
| 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[fuchsia_device_manager_DEVICE_PATH_MAX]; |
| 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) { |
| fbl::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[fuchsia_device_manager_DEVICE_PATH_MAX]; |
| 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 devhost. |
| while (itr && itr != sys_device_) { |
| if (itr->proxy()) { |
| // this device is in a child devhost |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| itr = itr->parent(); |
| } |
| if (!itr) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| } |
| |
| fbl::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; |
| } |
| |
| |
| zx_status_t fidl_DmMexec(void* ctx, zx_handle_t raw_kernel, zx_handle_t raw_bootdata) { |
| zx_status_t st; |
| constexpr size_t kBootdataExtraSz = PAGE_SIZE * 4; |
| |
| zx::vmo kernel(raw_kernel); |
| zx::vmo original_bootdata(raw_bootdata); |
| zx::vmo bootdata; |
| |
| zx::vmar root_vmar(zx_vmar_root_self()); |
| fzl::OwnedVmoMapper mapper; |
| |
| uint8_t* buffer = new uint8_t[kBootdataExtraSz]; |
| memset(buffer, 0, kBootdataExtraSz); |
| fbl::unique_ptr<uint8_t[]> deleter; |
| deleter.reset(buffer); |
| |
| size_t original_size; |
| st = original_bootdata.get_size(&original_size); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: could not get bootdata vmo size, st = %d\n", st); |
| return st; |
| } |
| |
| st = original_bootdata.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, |
| original_size + PAGE_SIZE * 4, &bootdata); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: failed to clone bootdata st = %d\n", st); |
| return st; |
| } |
| |
| size_t vmo_size; |
| st = bootdata.get_size(&vmo_size); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: failed to get new bootdata size, st = %d\n", st); |
| return st; |
| } |
| |
| auto dev = static_cast<Device*>(ctx); |
| st = zx_system_mexec_payload_get(dev->coordinator->root_resource().get(), buffer, |
| kBootdataExtraSz); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: mexec get payload returned %d\n", st); |
| return st; |
| } |
| |
| zx::vmo mapped_bootdata; |
| st = bootdata.duplicate(ZX_RIGHT_SAME_RIGHTS, &mapped_bootdata); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: failed to duplicate bootdata handle, st = %d\n", st); |
| return st; |
| } |
| |
| st = mapper.Map(std::move(mapped_bootdata)); |
| if (st != ZX_OK) { |
| log(ERROR, "dm_mexec: failed to map bootdata vmo, st = %d\n", st); |
| return st; |
| } |
| |
| void* bootdata_ptr = mapper.start(); |
| zbi::Zbi bootdata_zbi(static_cast<uint8_t*>(bootdata_ptr), vmo_size); |
| zbi::Zbi mexec_payload_zbi(buffer); |
| |
| zbi_result_t zbi_st = bootdata_zbi.Extend(mexec_payload_zbi); |
| if (zbi_st != ZBI_RESULT_OK) { |
| log(ERROR, "dm_mexec: failed to extend bootdata zbi, st = %d\n", zbi_st); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| dev->coordinator->DmMexec(std::move(kernel), std::move(bootdata)); |
| return ZX_OK; |
| } |
| |
| zx_status_t fidl_DirectoryWatch(void* ctx, uint32_t mask, uint32_t options, |
| zx_handle_t raw_watcher, fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| zx::channel watcher(raw_watcher); |
| |
| if (mask & (~fuchsia_io_WATCH_MASK_ALL) || options != 0) { |
| return fuchsia_device_manager_CoordinatorDirectoryWatch_reply(txn, ZX_ERR_INVALID_ARGS); |
| } |
| |
| zx_status_t status = devfs_watch(dev->self, std::move(watcher), mask); |
| return fuchsia_device_manager_CoordinatorDirectoryWatch_reply(txn, status); |
| } |
| |
| // send message to devhost, requesting the creation of a device |
| static zx_status_t dh_create_device(const fbl::RefPtr<Device>& dev, Devhost* dh, const char* args, |
| zx::handle rpc_proxy) { |
| zx_status_t r; |
| |
| zx::channel hrpc, hrpc_remote; |
| if ((r = zx::channel::create(0, &hrpc, &hrpc_remote)) != ZX_OK) { |
| return r; |
| } |
| |
| if (dev->libname().size() != 0) { |
| zx::vmo vmo; |
| if ((r = dev->coordinator->LibnameToVmo(dev->libname(), &vmo)) != ZX_OK) { |
| return r; |
| } |
| |
| r = dh_send_create_device(dev.get(), dh, std::move(hrpc_remote), std::move(vmo), args, |
| std::move(rpc_proxy)); |
| if (r != ZX_OK) { |
| return r; |
| } |
| } else { |
| r = dh_send_create_device_stub(dev.get(), dh, std::move(hrpc_remote), dev->protocol_id()); |
| if (r != ZX_OK) { |
| return r; |
| } |
| } |
| |
| dev->set_channel(std::move(hrpc)); |
| if ((r = Device::BeginWait(dev, dev->coordinator->dispatcher())) != ZX_OK) { |
| return r; |
| } |
| dh->devices().push_back(dev.get()); |
| return ZX_OK; |
| } |
| |
| // send message to devhost, requesting the binding of a driver to a device |
| static zx_status_t dh_bind_driver(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; |
| } |
| status = dh_send_bind_driver(dev.get(), libname, std::move(vmo)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| dev->flags |= DEV_CTX_BOUND; |
| return ZX_OK; |
| } |
| |
| // Create the proxy node for the given device if it doesn't exist and ensure it |
| // has a devhost. If |target_devhost| is not nullptr and the proxy doesn't have |
| // a devhost yet, |target_devhost| will be used for it. Otherwise a new devhost |
| // will be created. |
| zx_status_t Coordinator::PrepareProxy(const fbl::RefPtr<Device>& dev, Devhost* target_devhost) { |
| 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) { |
| return ZX_ERR_INTERNAL; |
| } |
| size_t arg0len = arg1 - arg0; |
| arg1++; |
| |
| char devhostname[32]; |
| snprintf(devhostname, sizeof(devhostname), "devhost:%.*s", (int)arg0len, arg0); |
| |
| zx_status_t r; |
| if (dev->proxy() == nullptr && (r = dev->CreateProxy()) != ZX_OK) { |
| log(ERROR, "devcoord: cannot create proxy device: %d\n", r); |
| return r; |
| } |
| |
| // if this device has no devhost, 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) { |
| log(ERROR, "devcoordinator: cannot create proxy rpc channel: %d\n", r); |
| return r; |
| } |
| } |
| if (target_devhost == nullptr) { |
| if ((r = NewDevhost(devhostname, dev->host(), &target_devhost)) < 0) { |
| log(ERROR, "devcoordinator: NewDevhost: %d\n", r); |
| return r; |
| } |
| } |
| dev->proxy()->set_host(target_devhost); |
| if ((r = dh_create_device(dev->proxy(), dev->proxy()->host(), arg1, std::move(h1))) < 0) { |
| log(ERROR, "devcoordinator: dh_create_device: %d\n", r); |
| return r; |
| } |
| if (need_proxy_rpc) { |
| if ((r = dh_send_connect_proxy(dev.get(), std::move(h0))) < 0) { |
| log(ERROR, "devcoordinator: dh_send_connect_proxy: %d\n", r); |
| } |
| } |
| if (dev == sys_device_) { |
| if ((r = fdio_service_connect(kItemsPath, h0.release())) != ZX_OK) { |
| log(ERROR, "devcoordinator: fdio_service_connect %s: %d\n", kItemsPath, r); |
| } |
| } |
| zx::channel client_remote = dev->take_client_remote(); |
| if (client_remote.is_valid()) { |
| if ((r = devfs_connect(dev->proxy().get(), std::move(client_remote))) != ZX_OK) { |
| log(ERROR, "devcoordinator: devfs_connnect: %d\n", 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))) { |
| return ZX_ERR_BAD_STATE; |
| } |
| if (!(dev->flags & DEV_CTX_MUST_ISOLATE)) { |
| // non-busdev is pretty simple |
| if (dev->host() == nullptr) { |
| log(ERROR, "devcoordinator: can't bind to device without devhost\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return dh_bind_driver(dev, drv->libname.c_str()); |
| } |
| |
| zx_status_t r; |
| if ((r = PrepareProxy(dev, nullptr /* target_devhost */)) < 0) { |
| return r; |
| } |
| |
| r = dh_bind_driver(dev->proxy(), drv->libname.c_str()); |
| // TODO(swetland): arrange to mark us unbound when the proxy (or its devhost) 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(), std::move(client_remote)); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: devfs_connnect: %d\n", status); |
| } |
| } |
| } |
| // TODO(tesienbe): We probably should do something with the return value |
| // from this... |
| BindDevice(dev, fbl::StringPiece("") /* autobind */, true /* new device */); |
| } |
| |
| static int suspend_timeout_thread(void* arg) { |
| // 10 seconds |
| zx_nanosleep(zx_deadline_after(ZX_SEC(10))); |
| |
| auto coordinator = static_cast<Coordinator*>(arg); |
| auto ctx = &coordinator->suspend_context(); |
| if (coordinator->suspend_debug()) { |
| if (ctx->flags() == SuspendContext::Flags::kRunning) { |
| return 0; // success |
| } |
| log(ERROR, "devcoordinator: suspend time out\n"); |
| log(ERROR, " sflags: 0x%08x\n", ctx->sflags()); |
| } |
| if (coordinator->suspend_fallback()) { |
| suspend_fallback(coordinator->root_resource(), ctx->sflags()); |
| } |
| return 0; |
| } |
| |
| void Coordinator::Suspend(SuspendContext ctx) { |
| // The sys device should have a proxy. If not, the system hasn't fully initialized yet and |
| // cannot go to suspend. |
| if (!sys_device_->proxy()) { |
| return; |
| } |
| if (suspend_context().flags() == SuspendContext::Flags::kSuspend) { |
| return; |
| } |
| |
| suspend_context() = std::move(ctx); |
| |
| auto completion = [this](zx_status_t status) { |
| auto& ctx = suspend_context(); |
| if (status != ZX_OK) { |
| // TODO: unroll suspend |
| // do not continue to suspend as this indicates a driver suspend |
| // problem and should show as a bug |
| log(ERROR, "devcoordinator: failed to suspend: %s\n", zx_status_get_string(status)); |
| if (ctx.sflags() == DEVICE_SUSPEND_FLAG_MEXEC) { |
| ctx.kernel().signal(0, ZX_USER_SIGNAL_0); |
| } |
| ctx.set_flags(devmgr::SuspendContext::Flags::kRunning); |
| return; |
| } |
| |
| if (ctx.sflags() == DEVICE_SUSPEND_FLAG_MEXEC) { |
| zx_system_mexec(root_resource().get(), ctx.kernel().get(), ctx.bootdata().get()); |
| } else { |
| // should never get here on x86 |
| // on arm, if the platform driver does not implement |
| // suspend go to the kernel fallback |
| ::suspend_fallback(root_resource(), ctx.sflags()); |
| // if we get here the system did not suspend successfully |
| ctx.set_flags(devmgr::SuspendContext::Flags::kRunning); |
| } |
| }; |
| |
| // We don't need to suspend anything except sys_device and it's children, |
| // since we do not run suspend hooks for children of test or misc |
| auto task = SuspendTask::Create(sys_device(), ctx.sflags(), std::move(completion)); |
| suspend_context().set_task(std::move(task)); |
| |
| if (suspend_fallback() || suspend_debug()) { |
| thrd_t t; |
| int ret = |
| thrd_create_with_name(&t, suspend_timeout_thread, this, "devcoord-suspend-timeout"); |
| if (ret != thrd_success) { |
| log(ERROR, "devcoordinator: failed to create suspend timeout thread\n"); |
| } else { |
| thrd_detach(t); |
| } |
| } |
| } |
| |
| void Coordinator::Suspend(uint32_t flags) { |
| if ((flags & DEVICE_SUSPEND_REASON_MASK) != DEVICE_SUSPEND_FLAG_SUSPEND_RAM) { |
| vfs_exit(fshost_event()); |
| } |
| |
| Suspend(SuspendContext(SuspendContext::Flags::kSuspend, flags)); |
| } |
| |
| void Coordinator::DmMexec(zx::vmo kernel, zx::vmo bootdata) { |
| Suspend(SuspendContext(SuspendContext::Flags::kSuspend, DEVICE_SUSPEND_FLAG_MEXEC, |
| std::move(kernel), std::move(bootdata))); |
| } |
| |
| fbl::unique_ptr<Driver> Coordinator::ValidateDriver(fbl::unique_ptr<Driver> drv) { |
| if ((drv->flags & ZIRCON_DRIVER_NOTE_FLAG_ASAN) && !config_.asan_drivers) { |
| if (launched_first_devhost_) { |
| log(ERROR, |
| "%s (%s) requires ASan: cannot load after boot;" |
| " consider devmgr.devhost.asan=true\n", |
| 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) { |
| auto driver = ValidateDriver(fbl::unique_ptr<Driver>(drv)); |
| if (!driver) { |
| return; |
| } |
| async::PostTask(dispatcher(), [this, drv = driver.release()] { |
| drivers_.push_back(drv); |
| zx_status_t status = BindDriver(drv); |
| if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) { |
| log(ERROR, "devcoordinator: failed to bind driver '%s': %s\n", drv->name.data(), |
| zx_status_get_string(status)); |
| } |
| }); |
| } |
| |
| // 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(fbl::unique_ptr<Driver>(drv)); |
| if (!driver) { |
| return; |
| } |
| |
| // Record the special component driver when we see it |
| if (!strcmp(driver->libname.data(), kComponentDriverPath)) { |
| component_driver_ = driver.get(); |
| driver->never_autoselect = true; |
| } |
| |
| if (version[0] == '*') { |
| // fallback driver, load only if all else fails |
| fallback_drivers_.push_front(driver.release()); |
| } else if (version[0] == '!') { |
| // debugging / development hack |
| // prioritize drivers with version "!..." over others |
| drivers_.push_front(driver.release()); |
| } else { |
| drivers_.push_back(driver.release()); |
| } |
| } |
| |
| // Drivers added during system scan (from the dedicated thread) |
| // are added to system_drivers for bulk processing once |
| // CTL_ADD_SYSTEM is sent. |
| // |
| // TODO: fancier priority management |
| void Coordinator::DriverAddedSys(Driver* drv, const char* version) { |
| auto driver = ValidateDriver(fbl::unique_ptr<Driver>(drv)); |
| if (!driver) { |
| return; |
| } |
| log(INFO, "devcoordinator: adding system driver '%s' '%s'\n", driver->name.data(), |
| driver->libname.data()); |
| if (load_vmo(driver->libname.data(), &driver->dso_vmo)) { |
| log(ERROR, "devcoordinator: system driver '%s' '%s' could not cache DSO\n", |
| driver->name.data(), driver->libname.data()); |
| } |
| if (version[0] == '*') { |
| // de-prioritize drivers that are "fallback" |
| system_drivers_.push_back(driver.release()); |
| } else { |
| system_drivers_.push_front(driver.release()); |
| } |
| } |
| |
| zx_status_t Coordinator::BindDriverToDevice(const fbl::RefPtr<Device>& dev, const Driver* drv, |
| bool autobind, const AttemptBindFunc& attempt_bind) { |
| if (!dev->is_bindable()) { |
| return ZX_ERR_NEXT; |
| } |
| if (!driver_is_bindable(drv, dev->protocol_id(), dev->props(), autobind)) { |
| return ZX_ERR_NEXT; |
| } |
| |
| zx_status_t status = attempt_bind(drv, dev); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to bind drv='%s' to dev='%s': %s\n", 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; |
| } |
| // TODO(FLK-299): Remove this once the root cause is found. |
| char path[fuchsia_device_manager_DEVICE_PATH_MAX + 1]; |
| zx_status_t topo_status = GetTopologicalPath(dev, path, sizeof(path)); |
| if (topo_status == ZX_OK && strstr(path, "misc/ramctl") != nullptr) { |
| printf("[%ld ms] (ramctl) BindDriver: %s to %s\n", |
| zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data(), path); |
| } |
| 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) { |
| if (drv->never_autoselect) { |
| return ZX_OK; |
| } |
| 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) { |
| if (status == ZX_OK) { |
| // TODO(FLK-299): Remove this once the root cause is found. |
| printf("[%ld ms] (misc) BindDriver: %s\n", |
| zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data()); |
| } |
| return status; |
| } |
| status = BindDriverToDevice(test_device_, drv, true /* autobind */, attempt_bind); |
| if (status != ZX_ERR_NEXT) { |
| return status; |
| } |
| if (!running_) { |
| return ZX_ERR_UNAVAILABLE; |
| } |
| printf("devcoordinator: driver '%s' added\n", drv->name.data()); |
| for (auto& dev : devices_) { |
| zx_status_t status = |
| BindDriverToDevice(fbl::WrapRefPtr(&dev), drv, true /* autobind */, attempt_bind); |
| if (status == ZX_ERR_NEXT) { |
| continue; |
| } |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::BindDevice(const fbl::RefPtr<Device>& dev, fbl::StringPiece 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) { |
| for (auto& composite : composite_devices_) { |
| size_t index; |
| if (composite.TryMatchComponents(dev, &index)) { |
| log(SPEW, "devcoordinator: dev='%s' matched component %zu of composite='%s'\n", |
| dev->name().data(), index, composite.name().data()); |
| |
| return composite.BindComponent(index, dev); |
| } |
| } |
| } |
| |
| // TODO: disallow if we're in the middle of enumeration, etc |
| for (const auto& drv : drivers_) { |
| if (!autobind && drvlibname.compare(drv.libname)) { |
| continue; |
| } |
| if (drv.never_autoselect) { |
| continue; |
| } |
| |
| zx_status_t status = BindDriverToDevice(dev, &drv, autobind); |
| if (status == ZX_ERR_NEXT) { |
| continue; |
| } |
| |
| // If the device supports multibind (this is a devmgr-internal setting), |
| // keep trying to match more drivers even if one fails. |
| if (!(dev->flags & DEV_CTX_MULTI_BIND)) { |
| if (status != ZX_OK) { |
| return status; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| // 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; |
| } |
| |
| zx_status_t Coordinator::ScanSystemDrivers() { |
| if (system_loaded_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| system_loaded_ = true; |
| // Fire up a thread to scan/load system drivers. |
| // This avoids deadlocks between the devhosts hosting the block devices that |
| // these drivers may be served from and the devcoordinator loading them. |
| thrd_t t; |
| auto callback = [](void* arg) { |
| auto coordinator = static_cast<Coordinator*>(arg); |
| find_loadable_drivers("/system/driver", |
| fit::bind_member(coordinator, &Coordinator::DriverAddedSys)); |
| async::PostTask(coordinator->dispatcher(), |
| [coordinator] { coordinator->BindSystemDrivers(); }); |
| return 0; |
| }; |
| int ret = thrd_create_with_name(&t, callback, this, "system-driver-loader"); |
| if (ret != thrd_success) { |
| log(ERROR, "devcoordinator: failed to create system driver scanning thread\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| thrd_detach(t); |
| return ZX_OK; |
| } |
| |
| void Coordinator::BindSystemDrivers() { |
| Driver* drv; |
| // Bind system drivers. |
| while ((drv = system_drivers_.pop_front()) != nullptr) { |
| drivers_.push_back(drv); |
| zx_status_t status = BindDriver(drv); |
| if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) { |
| log(ERROR, "devcoordinator: failed to bind driver '%s': %s\n", drv->name.data(), |
| zx_status_get_string(status)); |
| } |
| } |
| // Bind remaining fallback drivers. |
| while ((drv = fallback_drivers_.pop_front()) != nullptr) { |
| printf("devcoordinator: fallback driver '%s' is available\n", drv->name.data()); |
| drivers_.push_back(drv); |
| zx_status_t status = BindDriver(drv); |
| if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) { |
| log(ERROR, "devcoordinator: failed to bind driver '%s': %s\n", drv->name.data(), |
| zx_status_get_string(status)); |
| } |
| } |
| } |
| |
| void Coordinator::BindDrivers() { |
| for (Driver& drv : drivers_) { |
| zx_status_t status = BindDriver(&drv); |
| if (status != ZX_OK && status != ZX_ERR_UNAVAILABLE) { |
| log(ERROR, "devcoordinator: failed to bind driver '%s': %s\n", drv.name.data(), |
| zx_status_get_string(status)); |
| } |
| } |
| } |
| |
| void Coordinator::UseFallbackDrivers() { |
| drivers_.splice(drivers_.end(), fallback_drivers_); |
| } |
| |
| void Coordinator::InitOutgoingServices() { |
| const auto& public_dir = outgoing_services_.public_dir(); |
| |
| const auto admin = [this](zx::channel request) { |
| static_assert(fuchsia_device_manager_SUSPEND_FLAG_REBOOT == DEVICE_SUSPEND_FLAG_REBOOT); |
| static_assert(fuchsia_device_manager_SUSPEND_FLAG_POWEROFF == DEVICE_SUSPEND_FLAG_POWEROFF); |
| |
| static constexpr fuchsia_device_manager_Administrator_ops_t kOps = { |
| .Suspend = [](void* ctx, uint32_t flags, fidl_txn_t* txn) { |
| static_cast<Coordinator*>(ctx)->Suspend(flags); |
| return fuchsia_device_manager_AdministratorSuspend_reply(txn, ZX_OK); |
| }, |
| }; |
| |
| const auto status = fidl_bind( |
| this->config_.dispatcher, request.release(), |
| reinterpret_cast<fidl_dispatch_t*>(fuchsia_device_manager_Administrator_dispatch), this, |
| &kOps); |
| if (status != ZX_OK) { |
| printf("Failed to bind to client channel: %d \n", status); |
| } |
| return status; |
| }; |
| public_dir->AddEntry(fuchsia_device_manager_Administrator_Name, |
| fbl::MakeRefCounted<fs::Service>(admin)); |
| |
| const auto debug = [this](zx::channel request) { |
| static constexpr fuchsia_device_manager_DebugDumper_ops_t kOps = { |
| .DumpTree = |
| [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) { |
| VmoWriter writer{zx::vmo(vmo)}; |
| static_cast<Coordinator*>(ctx)->DumpState(&writer); |
| return fuchsia_device_manager_DebugDumperDumpTree_reply( |
| txn, writer.status(), writer.written(), writer.available()); |
| }, |
| .DumpDrivers = |
| [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) { |
| VmoWriter writer{zx::vmo(vmo)}; |
| static_cast<Coordinator*>(ctx)->DumpDrivers(&writer); |
| return fuchsia_device_manager_DebugDumperDumpDrivers_reply( |
| txn, writer.status(), writer.written(), writer.available()); |
| }, |
| .DumpBindingProperties = |
| [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) { |
| VmoWriter writer{zx::vmo(vmo)}; |
| static_cast<Coordinator*>(ctx)->DumpGlobalDeviceProps(&writer); |
| return fuchsia_device_manager_DebugDumperDumpBindingProperties_reply( |
| txn, writer.status(), writer.written(), writer.available()); |
| }, |
| }; |
| |
| auto status = fidl_bind( |
| this->config_.dispatcher, request.release(), |
| reinterpret_cast<fidl_dispatch_t*>(fuchsia_device_manager_DebugDumper_dispatch), this, |
| &kOps); |
| if (status != ZX_OK) { |
| printf("Failed to bind to client channel: %d \n", status); |
| } |
| return status; |
| }; |
| public_dir->AddEntry(fuchsia_device_manager_DebugDumper_Name, |
| fbl::MakeRefCounted<fs::Service>(debug)); |
| } |
| |
| zx_status_t Coordinator::BindOutgoingServices(zx::channel listen_on) { |
| return outgoing_services_.Serve(std::move(listen_on)); |
| } |
| |
| } // namespace devmgr |