| // 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/unique_ptr.h> |
| #include <fuchsia/device/manager/c/fidl.h> |
| #include <fuchsia/io/c/fidl.h> |
| #include <launchpad/launchpad.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/io.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 <lib/zx/socket.h> |
| #include <libzbi/zbi-cpp.h> |
| #include <zircon/assert.h> |
| #include <zircon/boot/bootdata.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/policy.h> |
| #include <zircon/syscalls/system.h> |
| |
| #include <utility> |
| |
| #include "devhost-loader-service.h" |
| #include "devmgr.h" |
| #include "../shared/env.h" |
| #include "../shared/fdio.h" |
| #include "../shared/fidl_txn.h" |
| #include "../shared/log.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 kBootFirmwareDir[] = "/boot/lib/firmware"; |
| constexpr char kSystemFirmwareDir[] = "/system/lib/firmware"; |
| |
| // 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("devmgr: Failed to signal VFS exit\n"); |
| return; |
| } else if ((status = fshost_event.wait_one(FSHOST_SIGNAL_EXIT_DONE, |
| zx::deadline_after(zx::sec(5)), |
| nullptr)) != ZX_OK) { |
| printf("devmgr: Failed to wait for VFS exit completion\n"); |
| } |
| } |
| |
| zx_status_t suspend_devhost(devmgr::Devhost* dh, devmgr::SuspendContext* ctx) { |
| if (dh->devices().is_empty()) { |
| return ZX_OK; |
| } |
| devmgr::Device* dev = &dh->devices().front(); |
| |
| if (!(dev->flags & DEV_CTX_PROXY)) { |
| log(INFO, "devcoord: devhost root '%s' (%p) is not a proxy\n", dev->name.data(), dev); |
| return ZX_ERR_BAD_STATE; |
| } |
| log(DEVLC, "devcoord: suspend devhost %p device '%s' (%p)\n", dh, dev->name.data(), dev); |
| |
| zx_status_t status = dh_send_suspend(dev, ctx->sflags()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| dh->flags() |= devmgr::Devhost::Flags::kSuspend; |
| // TODO(teisenbe/kulakowski) Make SuspendContext automatically refcounted. |
| ctx->AddRef(); |
| return ZX_OK; |
| } |
| |
| void process_suspend_list(devmgr::SuspendContext* ctx) { |
| auto dh = ctx->devhosts().make_iterator(*ctx->dh()); |
| devmgr::Devhost* parent = nullptr; |
| do { |
| if (!parent || (dh->parent() == parent)) { |
| // send Message::Op::kSuspend each set of children of a devhost at a time, |
| // since they can run in parallel |
| suspend_devhost(dh.CopyPointer(), &ctx->coordinator()->suspend_context()); |
| parent = dh->parent(); |
| } else { |
| // if the parent is different than the previous devhost's |
| // parent, either this devhost is the parent, a child of |
| // its parent's sibling, or the parent's sibling, so stop |
| // processing until all the outstanding suspends are done |
| parent = nullptr; |
| break; |
| } |
| } while (++dh != ctx->devhosts().end()); |
| // next devhost to process once all the outstanding suspends are done |
| if (dh.IsValid()) { |
| ctx->set_dh(dh.CopyPointer()); |
| } else { |
| ctx->set_dh(nullptr); |
| ctx->devhosts().clear(); |
| } |
| } |
| |
| void suspend_fallback(const zx::resource& root_resource, uint32_t flags) { |
| log(INFO, "devcoord: 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); |
| } |
| } |
| |
| void ContinueSuspend(devmgr::SuspendContext* ctx, const zx::resource& root_resource) { |
| if (ctx->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, "devcoord: failed to suspend\n"); |
| // notify dmctl |
| ctx->CloseSocket(); |
| 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->Release()) { |
| if (ctx->dh() != nullptr) { |
| process_suspend_list(ctx); |
| } else 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()); |
| // this handle is leaked on the shutdown path for x86 |
| ctx->CloseSocket(); |
| // if we get here the system did not suspend successfully |
| ctx->set_flags(devmgr::SuspendContext::Flags::kRunning); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| namespace devmgr { |
| |
| uint32_t log_flags = LOG_ERROR | LOG_INFO; |
| |
| Coordinator::Coordinator(CoordinatorConfig config) : config_(std::move(config)) {} |
| |
| Coordinator::~Coordinator() { drivers_.clear(); } |
| |
| bool Coordinator::InSuspend() const { |
| return suspend_context_.flags() == SuspendContext::Flags::kSuspend; |
| } |
| |
| zx_status_t Coordinator::InitializeCoreDevices() { |
| fbl::AllocChecker ac; |
| { |
| root_device_.flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND; |
| root_device_.protocol_id = ZX_PROTOCOL_ROOT; |
| root_device_.name = fbl::String("root", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| root_device_.args = fbl::String("root,", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| root_device_.AddRef(); |
| } |
| |
| { |
| misc_device_.parent = &root_device_; |
| misc_device_.flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND; |
| misc_device_.protocol_id = ZX_PROTOCOL_MISC_PARENT; |
| misc_device_.name = fbl::String("misc", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| misc_device_.args = fbl::String("misc,", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| misc_device_.AddRef(); |
| } |
| |
| { |
| sys_device_.parent = &root_device_; |
| sys_device_.flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE; |
| sys_device_.name = fbl::String("sys", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| sys_device_.args = fbl::String("sys,", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| sys_device_.AddRef(); |
| } |
| |
| { |
| test_device_.parent = &root_device_; |
| test_device_.flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND; |
| test_device_.protocol_id = ZX_PROTOCOL_TEST_PARENT; |
| test_device_.name = fbl::String("test", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| test_device_.args = fbl::String("test,", &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| test_device_.AddRef(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::OpenVirtcon(zx::channel virtcon_receiver) const { |
| zx_handle_t raw_virtcon_receiver = virtcon_receiver.release(); |
| return virtcon_channel_.write(0, nullptr, 0, &raw_virtcon_receiver, 1); |
| } |
| |
| void Coordinator::DmPrintf(const char* fmt, ...) const { |
| if (!dmctl_socket_.is_valid()) { |
| return; |
| } |
| char buf[1024]; |
| va_list ap; |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| size_t actual; |
| zx_status_t status = dmctl_socket_.write(0, buf, strlen(buf), &actual); |
| if (status != ZX_OK) { |
| dmctl_socket_.reset(); |
| } |
| } |
| |
| zx_status_t Coordinator::HandleDmctlWrite(size_t len, const char* cmd) { |
| if (len == 4) { |
| if (!memcmp(cmd, "dump", 4)) { |
| DumpState(); |
| return ZX_OK; |
| } |
| if (!memcmp(cmd, "help", 4)) { |
| DmPrintf("dump - dump device tree\n" |
| "poweroff - power off the system\n" |
| "shutdown - power off the system\n" |
| "suspend - suspend the system to RAM\n" |
| "reboot - reboot the system\n" |
| "reboot-bootloader - reboot the system into bootloader\n" |
| "reboot-recovery - reboot the system into recovery\n" |
| "kerneldebug - send a command to the kernel\n" |
| "ktraceoff - stop kernel tracing\n" |
| "ktraceon - start kernel tracing\n" |
| "devprops - dump published devices and their binding properties\n" |
| "drivers - list discovered drivers and their properties\n"); |
| return ZX_OK; |
| } |
| } |
| if ((len == 7) && !memcmp(cmd, "drivers", 7)) { |
| DumpDrivers(); |
| return ZX_OK; |
| } |
| if (len == 8) { |
| if (!memcmp(cmd, "ktraceon", 8)) { |
| zx_ktrace_control(root_resource().get(), KTRACE_ACTION_START, KTRACE_GRP_ALL, nullptr); |
| return ZX_OK; |
| } |
| if (!memcmp(cmd, "devprops", 8)) { |
| DumpGlobalDeviceProps(); |
| return ZX_OK; |
| } |
| } |
| if ((len == 9) && (!memcmp(cmd, "ktraceoff", 9))) { |
| zx_ktrace_control(root_resource().get(), KTRACE_ACTION_STOP, 0, nullptr); |
| zx_ktrace_control(root_resource().get(), KTRACE_ACTION_REWIND, 0, nullptr); |
| return ZX_OK; |
| } |
| if ((len > 12) && !memcmp(cmd, "kerneldebug ", 12)) { |
| return zx_debug_send_command(root_resource().get(), cmd + 12, len - 12); |
| } |
| |
| if (InSuspend()) { |
| log(ERROR, "devcoord: rpc: dm-command \"%.*s\" forbidden in suspend\n", |
| static_cast<uint32_t>(len), cmd); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if ((len == 6) && !memcmp(cmd, "reboot", 6)) { |
| vfs_exit(fshost_event()); |
| Suspend(DEVICE_SUSPEND_FLAG_REBOOT); |
| return ZX_OK; |
| } |
| if ((len == 17) && !memcmp(cmd, "reboot-bootloader", 17)) { |
| vfs_exit(fshost_event()); |
| Suspend(DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER); |
| return ZX_OK; |
| } |
| if ((len == 15) && !memcmp(cmd, "reboot-recovery", 15)) { |
| vfs_exit(fshost_event()); |
| Suspend(DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY); |
| return ZX_OK; |
| } |
| if ((len == 7) && !memcmp(cmd, "suspend", 7)) { |
| Suspend(DEVICE_SUSPEND_FLAG_SUSPEND_RAM); |
| return ZX_OK; |
| } |
| if (len == 8 && (!memcmp(cmd, "poweroff", 8) || !memcmp(cmd, "shutdown", 8))) { |
| vfs_exit(fshost_event()); |
| Suspend(DEVICE_SUSPEND_FLAG_POWEROFF); |
| return ZX_OK; |
| } |
| if ((len > 11) && !memcmp(cmd, "add-driver:", 11)) { |
| len -= 11; |
| char path[len + 1]; |
| memcpy(path, cmd + 11, len); |
| path[len] = 0; |
| load_driver(path, fit::bind_member(this, &Coordinator::DriverAdded)); |
| return ZX_OK; |
| } |
| DmPrintf("unknown command\n"); |
| log(ERROR, "dmctl: unknown command '%.*s'\n", (int) len, cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| 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, "devcoord: cannot open driver '%s'\n", libname.data()); |
| return ZX_ERR_IO; |
| } |
| zx::vmo vmo; |
| zx_status_t r = fdio_get_vmo_clone(fd, vmo.reset_and_get_address()); |
| close(fd); |
| if (r < 0) { |
| log(ERROR, "devcoord: cannot get driver vmo '%s'\n", libname.data()); |
| } |
| 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, "devcoord: 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, "devcoord: cannot duplicate cached dso for '%s' '%s'\n", drv->name.data(), |
| libname.data()); |
| } |
| return r; |
| } else { |
| return load_vmo(libname, out_vmo); |
| } |
| } |
| |
| zx_status_t Coordinator::SetBootdata(const zx::unowned_vmo& vmo) { |
| if (bootdata_vmo_.is_valid()) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| return vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &bootdata_vmo_); |
| } |
| |
| void Coordinator::DumpDevice(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 ref=%d", dev, dev->refcount_); |
| } else { |
| extra[0] = 0; |
| } |
| if (pid == 0) { |
| DmPrintf("%*s[%s]%s\n", (int) (indent * 3), "", dev->name.data(), extra); |
| } else { |
| DmPrintf("%*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(dev->proxy, indent); |
| } |
| for (const auto& child : dev->children) { |
| DumpDevice(&child, indent + 1); |
| } |
| } |
| |
| void Coordinator::DumpState() const { |
| DumpDevice(&root_device_, 0); |
| DumpDevice(&misc_device_, 1); |
| DumpDevice(&sys_device_, 1); |
| DumpDevice(&test_device_, 1); |
| } |
| |
| void Coordinator::DumpDeviceProps(const Device* dev) const { |
| if (dev->host) { |
| DmPrintf("Name [%s]%s%s%s\n", |
| dev->name.data(), |
| dev->libname.empty() ? "" : " Driver [", |
| dev->libname.empty() ? "" : dev->libname.data(), |
| dev->libname.empty() ? "" : "]"); |
| DmPrintf("Flags :%s%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_ZOMBIE ? " Zombie" : "", |
| 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); |
| DmPrintf("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); |
| |
| DmPrintf("%u Propert%s\n", dev->prop_count, dev->prop_count == 1 ? "y" : "ies"); |
| for (uint32_t i = 0; i < dev->prop_count; ++i) { |
| const zx_device_prop_t* p = &dev->props[i]; |
| const char* param_name = di_bind_param_name(p->id); |
| |
| if (param_name) { |
| DmPrintf("[%2u/%2u] : Value 0x%08x Id %s\n", |
| i, dev->prop_count, p->value, param_name); |
| } else { |
| DmPrintf("[%2u/%2u] : Value 0x%08x Id 0x%04hx\n", |
| i, dev->prop_count, p->value, p->id); |
| } |
| } |
| DmPrintf("\n"); |
| } |
| |
| if (dev->proxy) { |
| DumpDeviceProps(dev->proxy); |
| } |
| for (const auto& child : dev->children) { |
| DumpDeviceProps(&child); |
| } |
| } |
| |
| void Coordinator::DumpGlobalDeviceProps() const { |
| DumpDeviceProps(&root_device_); |
| DumpDeviceProps(&misc_device_); |
| DumpDeviceProps(&sys_device_); |
| DumpDeviceProps(&test_device_); |
| } |
| |
| void Coordinator::DumpDrivers() const { |
| bool first = true; |
| for (const auto& drv : drivers_) { |
| DmPrintf("%sName : %s\n", first ? "" : "\n", drv.name.c_str()); |
| DmPrintf("Driver : %s\n", !drv.libname.empty() ? drv.libname.c_str() : "(null)"); |
| DmPrintf("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])); |
| DmPrintf("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)); |
| DmPrintf("[%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::GetTopoPath(const Device* dev, char* out, size_t max) const { |
| // TODO: Remove VLA. |
| char tmp[max]; |
| char* path = tmp + max - 1; |
| *path = 0; |
| size_t total = 1; |
| |
| while (dev != nullptr) { |
| if (dev->flags & DEV_CTX_PROXY) { |
| dev = dev->parent; |
| } |
| |
| const char* name; |
| if (dev->parent) { |
| name = dev->name.data(); |
| } else if (misc_device_.name == dev->name) { |
| name = "dev/misc"; |
| } else if (sys_device_.name == dev->name) { |
| name = "dev/sys"; |
| } else if (sys_device_.name == dev->name) { |
| name = "dev/test"; |
| } else { |
| name = "dev"; |
| } |
| |
| 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; |
| dev = dev->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, zx_handle_t hrpc, |
| const zx::resource& root_resource, const zx::job& sysinfo_job, |
| zx::unowned_job devhost_job) { |
| launchpad_t* lp; |
| launchpad_create_with_jobs(devhost_job->get(), 0, name, &lp); |
| launchpad_load_from_file(lp, devhost_bin); |
| launchpad_set_args(lp, 1, &devhost_bin); |
| |
| if (loader_service != nullptr) { |
| zx::channel connection; |
| zx_status_t status = loader_service->Connect(&connection); |
| if (status == ZX_OK) { |
| launchpad_use_loader_service(lp, connection.release()); |
| } |
| } |
| |
| launchpad_add_handle(lp, hrpc, PA_HND(PA_USER0, 0)); |
| |
| // Give devhosts the root resource if we have it (in tests, we may not) |
| //TODO: limit root resource to root devhost only |
| if (root_resource.is_valid()) { |
| zx::resource resource; |
| zx_status_t status = root_resource.duplicate(ZX_RIGHT_SAME_RIGHTS, &resource); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoord: failed to duplicate root resource: %d\n", status); |
| } |
| launchpad_add_handle(lp, resource.release(), PA_HND(PA_RESOURCE, 0)); |
| } |
| |
| // Inherit devmgr's environment (including kernel cmdline) |
| launchpad_clone(lp, LP_CLONE_ENVIRON); |
| |
| const char* nametable[2] = { "/boot", "/svc", }; |
| uint32_t name_count = 0; |
| |
| //TODO: eventually devhosts should not have vfs access |
| launchpad_add_handle(lp, fs_clone("boot").release(), |
| PA_HND(PA_NS_DIR, name_count++)); |
| |
| //TODO: constrain to /svc/device |
| zx::channel svc_channel = fs_clone("svc"); |
| if (svc_channel.is_valid()) { |
| launchpad_add_handle(lp, svc_channel.release(), PA_HND(PA_NS_DIR, name_count++)); |
| } |
| |
| launchpad_set_nametable(lp, name_count, nametable); |
| |
| //TODO: limit sysinfo job access to root devhost only |
| zx::job sysinfo_job_duplicate; |
| zx_status_t status = sysinfo_job.duplicate(ZX_RIGHT_SAME_RIGHTS, &sysinfo_job_duplicate); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoord: failed to duplicate sysinfo job: %d\n", status); |
| } |
| launchpad_add_handle(lp, sysinfo_job_duplicate.release(), PA_HND(PA_USER0, kIdHJobRoot)); |
| |
| const char* errmsg; |
| zx_handle_t proc; |
| status = launchpad_go(lp, &proc, &errmsg); |
| if (status < 0) { |
| log(ERROR, "devcoord: launch devhost '%s': failed: %d: %s\n", name, status, errmsg); |
| return status; |
| } |
| host->set_proc(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, "devcoord: 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 = fbl::make_unique<Devhost>(); |
| if (dh == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_handle_t hrpc, dh_hrpc; |
| zx_status_t r; |
| if ((r = zx_channel_create(0, &hrpc, &dh_hrpc)) < 0) { |
| return r; |
| } |
| dh->set_hrpc(dh_hrpc); |
| |
| if ((r = dc_launch_devhost(dh.get(), loader_service_, get_devhost_bin(config_.asan_drivers), |
| name, hrpc, root_resource(), config_.sysinfo_job, zx::unowned_job(config_.devhost_job))) < |
| 0) { |
| zx_handle_close(dh->hrpc()); |
| return r; |
| } |
| 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, "devcoord: new host %p\n", dh.get()); |
| |
| *out = dh.release(); |
| return ZX_OK; |
| } |
| |
| void Coordinator::ReleaseDevhost(Devhost* dh) { |
| if (!dh->Release()) { |
| return; |
| } |
| log(INFO, "devcoord: 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; |
| } |
| |
| // called when device children or proxys are removed |
| void Coordinator::ReleaseDevice(Device* dev) { |
| log(DEVLC, "devcoord: release dev %p name='%s' ref=%d\n", dev, dev->name.data(), |
| dev->refcount_); |
| |
| if (!dev->Release()) { |
| return; |
| } |
| |
| // Immortal devices are never destroyed |
| if (dev->flags & DEV_CTX_IMMORTAL) { |
| return; |
| } |
| |
| log(DEVLC, "devcoord: destroy dev %p name='%s'\n", dev, dev->name.data()); |
| |
| devfs_unpublish(dev); |
| |
| if (dev->hrpc.is_valid()) { |
| dev->wait.set_object(ZX_HANDLE_INVALID); |
| dev->hrpc.reset(); |
| } |
| dev->host = nullptr; |
| |
| fbl::unique_ptr<Metadata> md; |
| while ((md = dev->metadata.pop_front()) != nullptr) { |
| if (md->has_path) { |
| // return to published_metadata_ list |
| published_metadata_.push_back(std::move(md)); |
| } else { |
| // metadata was attached directly to this device, so we release it now |
| } |
| } |
| |
| //TODO: cancel any pending rpc responses |
| //TODO: Have dtor assert that DEV_CTX_IMMORTAL set on flags |
| delete dev; |
| } |
| |
| // 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(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) { |
| // 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, "devcoord: rpc: add-device '%.*s' forbidden in suspend\n", |
| static_cast<int>(name.size()), name.data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| log(RPC_IN, "devcoord: rpc: add-device '%.*s' args='%.*s'\n", |
| static_cast<int>(name.size()), name.data(), static_cast<int>(args.size()), args.data()); |
| |
| auto dev = fbl::make_unique<Device>(this); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| fbl::AllocChecker ac; |
| dev->name = fbl::String(name, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| dev->libname = fbl::String(driver_path, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| dev->args = fbl::String(args, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| dev->props = fbl::make_unique<zx_device_prop_t[]>(props_count); |
| if (!dev->props) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| dev->hrpc = std::move(rpc); |
| dev->protocol_id = protocol_id; |
| |
| dev->prop_count = static_cast<uint32_t>(props_count); |
| static_assert(sizeof(zx_device_prop_t) == sizeof(props_data[0])); |
| memcpy(dev->props.get(), props_data, props_count * sizeof(zx_device_prop_t)); |
| |
| // If we have bus device args we are, by definition, a bus device. |
| if (args.size() > 0) { |
| dev->flags |= DEV_CTX_MUST_ISOLATE; |
| } |
| |
| // We exist within our parent's device host |
| dev->host = parent->host; |
| |
| // If our parent is a proxy, for the purpose |
| // of devicefs, we need to work with *its* parent |
| // which is the device that it is proxying. |
| if (parent->flags & DEV_CTX_PROXY) { |
| parent = parent->parent; |
| } |
| dev->parent = parent; |
| |
| // We must mark the device as invisible before publishing so |
| // that we don't send "device added" notifications. |
| if (invisible) { |
| dev->flags |= DEV_CTX_INVISIBLE; |
| } |
| |
| zx_status_t r; |
| if ((r = devfs_publish(parent, dev.get())) < 0) { |
| return r; |
| } |
| |
| dev->wait.set_object(dev->hrpc.get()); |
| dev->wait.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED); |
| if ((r = dev->wait.Begin(dispatcher())) != ZX_OK) { |
| devfs_unpublish(dev.get()); |
| return r; |
| } |
| |
| if (dev->host) { |
| //TODO host == nullptr should be impossible |
| dev->host->AddRef(); |
| dev->host->devices().push_back(dev.get()); |
| } |
| dev->AddRef(); |
| parent->children.push_back(dev.get()); |
| parent->AddRef(); |
| |
| dev->client_remote = std::move(client_remote); |
| |
| devices_.push_back(dev.get()); |
| |
| log(DEVLC, "devcoord: dev %p name='%s' ++ref=%d (child)\n", |
| parent, parent->name.data(), parent->refcount_); |
| |
| log(DEVLC, "devcoord: publish %p '%s' props=%u args='%s' parent=%p\n", |
| dev.get(), dev->name.data(), dev->prop_count, dev->args.data(), dev->parent); |
| |
| if (!invisible) { |
| r = dev->publish_task.Post(dispatcher()); |
| if (r != ZX_OK) { |
| return r; |
| } |
| } |
| // TODO(teisenbe/kulakowski): This should go away once we switch to refptrs |
| // here |
| __UNUSED auto ptr = dev.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::MakeVisible(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->publish_task.Post(dispatcher()); |
| 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(Device* dev, bool forced) { |
| if (dev->flags & DEV_CTX_ZOMBIE) { |
| // This device was removed due to its devhost dying |
| // (process exit or some other channel on that devhost |
| // closing), and is now receiving the final remove call |
| dev->flags &= (~DEV_CTX_ZOMBIE); |
| ReleaseDevice(dev); |
| return ZX_OK; |
| } |
| if (dev->flags & DEV_CTX_DEAD) { |
| // This should not happen |
| log(ERROR, "devcoord: cannot remove dev %p name='%s' twice!\n", dev, dev->name.data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (dev->flags & DEV_CTX_IMMORTAL) { |
| // This too should not happen |
| log(ERROR, "devcoord: cannot remove dev %p name='%s' (immortal)\n", dev, dev->name.data()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| log(DEVLC, "devcoord: remove %p name='%s' parent=%p\n", dev, dev->name.data(), dev->parent); |
| dev->flags |= DEV_CTX_DEAD; |
| |
| // remove from devfs, preventing further OPEN attempts |
| devfs_unpublish(dev); |
| |
| if (dev->proxy) { |
| zx_status_t r = dh_send_remove_device(dev->proxy); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoord: failed to send message in dc_remove_device: %d\n", r); |
| } |
| } |
| |
| // detach from devhost |
| Devhost* dh = dev->host; |
| if (dh != nullptr) { |
| dev->host->devices().erase(*dev); |
| dev->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; |
| |
| Device* next; |
| Device* last = nullptr; |
| while (!dh->devices().is_empty()) { |
| next = &dh->devices().front(); |
| if (last == next) { |
| // This shouldn't be possible, but let's not infinite-loop if it happens |
| log(ERROR, "devcoord: fatal: failed to remove dev %p from devhost\n", next); |
| exit(1); |
| } |
| RemoveDevice(next, false); |
| last = 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 |
| Device* parent = dev->parent; |
| if (parent != nullptr) { |
| dev->parent = nullptr; |
| if (dev->flags & DEV_CTX_PROXY) { |
| parent->proxy = nullptr; |
| } else { |
| parent->children.erase(*dev); |
| 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 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, "devcoord: bus device %p name='%s' is unbound\n", |
| parent, parent->name.data()); |
| |
| if (parent->retries > 0) { |
| // Add device with an exponential backoff. |
| zx_status_t r = parent->publish_task.PostDelayed(dispatcher(), |
| parent->backoff); |
| if (r != ZX_OK) { |
| return r; |
| } |
| parent->backoff *= 2; |
| parent->retries--; |
| } |
| } |
| } |
| } |
| ReleaseDevice(parent); |
| } |
| |
| if (!(dev->flags & DEV_CTX_PROXY)) { |
| // remove from list of all devices |
| devices_.erase(*dev); |
| } |
| |
| if (forced) { |
| // release the ref held by the devhost |
| ReleaseDevice(dev); |
| } else { |
| // Mark the device as a zombie but don't drop the |
| // (likely) final reference. The caller needs to |
| // finish replying to the RPC and dropping the |
| // reference would close the RPC channel. |
| dev->flags |= DEV_CTX_ZOMBIE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::BindDevice(Device* dev, fbl::StringPiece drvlibname) { |
| log(INFO, "devcoord: dc_bind_device() '%.*s'\n", static_cast<int>(drvlibname.size()), |
| drvlibname.data()); |
| |
| // 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); |
| |
| //TODO: disallow if we're in the middle of enumeration, etc |
| for (const auto& drv : drivers_) { |
| if (autobind || !drvlibname.compare(drv.libname)) { |
| if (dc_is_bindable(&drv, dev->protocol_id, |
| dev->props.get(), dev->prop_count, autobind)) { |
| log(SPEW, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv.name.data(), dev->name.data()); |
| AttemptBind(&drv, dev); |
| return ZX_OK; |
| } |
| } |
| } |
| |
| // Notify observers that this device is available again |
| // Needed for non-auto-binding drivers like GPT against block, etc |
| if (autobind) { |
| devfs_advertise_modified(dev); |
| } |
| |
| return ZX_OK; |
| }; |
| |
| zx_status_t Coordinator::LoadFirmware(Device* dev, const char* path, zx::vmo* vmo, size_t* size) { |
| static const char* fwdirs[] = { |
| kBootFirmwareDir, |
| kSystemFirmwareDir, |
| }; |
| |
| // 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_status_t r = fdio_get_vmo_clone(fwfd, 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::GetMetadata(Device* dev, uint32_t type, void* buffer, size_t buflen, |
| size_t* actual) { |
| // search dev and its parent devices for a match |
| Device* test = dev; |
| while (test) { |
| for (const auto& md : test->metadata) { |
| if (md.type == type) { |
| if (md.length > buflen) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| memcpy(buffer, md.Data(), md.length); |
| *actual = md.length; |
| return ZX_OK; |
| } |
| } |
| test = test->parent; |
| } |
| |
| // if no metadata is found, check list of metadata added via device_publish_metadata() |
| char path[fuchsia_device_manager_PATH_MAX]; |
| zx_status_t status = GetTopoPath(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 (md.length > buflen) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| memcpy(buffer, md.Data(), md.length); |
| *actual = md.length; |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t Coordinator::GetMetadataSize(Device* dev, uint32_t type, size_t* size) { |
| // search dev and its parent devices for a match |
| Device* test = dev; |
| while (test) { |
| for (const auto& md : test->metadata) { |
| if (md.type == type) { |
| *size = md.length; |
| return ZX_OK; |
| } |
| } |
| test = test->parent; |
| } |
| |
| // if no metadata is found, check list of metadata added via device_publish_metadata() |
| char path[fuchsia_device_manager_PATH_MAX]; |
| zx_status_t status = GetTopoPath(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)) { |
| *size = md.length; |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t Coordinator::AddMetadata(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->metadata.push_front(std::move(md)); |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::PublishMetadata(Device* dev, const char* path, uint32_t type, |
| const void* data, uint32_t length) { |
| char caller_path[fuchsia_device_manager_PATH_MAX]; |
| zx_status_t status = GetTopoPath(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 { |
| // Adding metadata to arbitrary paths is restricted to drivers running in the sys devhost. |
| while (dev && dev != &sys_device_) { |
| if (dev->proxy) { |
| // this device is in a child devhost |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| dev = dev->parent; |
| } |
| if (!dev) { |
| 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; |
| } |
| |
| static zx_status_t fidl_AddDevice(void* ctx, zx_handle_t raw_rpc, |
| const uint64_t* props_data, size_t props_count, |
| const char* name_data, size_t name_size, |
| uint32_t protocol_id, |
| const char* driver_path_data, size_t driver_path_size, |
| const char* args_data, size_t args_size, |
| zx_handle_t raw_client_remote, fidl_txn_t* txn) { |
| auto parent = static_cast<Device*>(ctx); |
| zx::channel rpc(raw_rpc); |
| fbl::StringPiece name(name_data, name_size); |
| fbl::StringPiece driver_path(driver_path_data, driver_path_size); |
| fbl::StringPiece args(args_data, args_size); |
| zx::channel client_remote(raw_client_remote); |
| |
| zx_status_t status = parent->coordinator->AddDevice(parent, std::move(rpc), props_data, |
| props_count, name, protocol_id, driver_path, |
| args, false, std::move(client_remote)); |
| return fuchsia_device_manager_CoordinatorAddDevice_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_AddDeviceInvisible(void* ctx, zx_handle_t raw_rpc, |
| const uint64_t* props_data, size_t props_count, |
| const char* name_data, size_t name_size, |
| uint32_t protocol_id, |
| const char* driver_path_data, size_t driver_path_size, |
| const char* args_data, size_t args_size, |
| zx_handle_t raw_client_remote, fidl_txn_t* txn) { |
| auto parent = static_cast<Device*>(ctx); |
| zx::channel rpc(raw_rpc); |
| fbl::StringPiece name(name_data, name_size); |
| fbl::StringPiece driver_path(driver_path_data, driver_path_size); |
| fbl::StringPiece args(args_data, args_size); |
| zx::channel client_remote(raw_client_remote); |
| |
| zx_status_t status = parent->coordinator->AddDevice(parent, std::move(rpc), props_data, |
| props_count, name, protocol_id, driver_path, |
| args, true, std::move(client_remote)); |
| return fuchsia_device_manager_CoordinatorAddDeviceInvisible_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_RemoveDevice(void* ctx, fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| if (dev->coordinator->InSuspend()) { |
| log(ERROR, "devcoord: rpc: remove-device '%s' forbidden in suspend\n", dev->name.data()); |
| return fuchsia_device_manager_CoordinatorRemoveDevice_reply(txn, ZX_ERR_BAD_STATE); |
| } |
| |
| log(RPC_IN, "devcoord: rpc: remove-device '%s'\n", dev->name.data()); |
| // TODO(teisenbe): RemoveDevice and the reply func can return errors. We should probably |
| // act on it, but the existing code being migrated does not. |
| dev->coordinator->RemoveDevice(dev, false); |
| fuchsia_device_manager_CoordinatorRemoveDevice_reply(txn, ZX_OK); |
| |
| // Return STOP to signal we are done with this channel |
| return ZX_ERR_STOP; |
| } |
| |
| static zx_status_t fidl_MakeVisible(void* ctx, fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| if (dev->coordinator->InSuspend()) { |
| log(ERROR, "devcoord: rpc: make-visible '%s' forbidden in suspend\n", dev->name.data()); |
| return fuchsia_device_manager_CoordinatorMakeVisible_reply(txn, ZX_ERR_BAD_STATE); |
| } |
| log(RPC_IN, "devcoord: rpc: make-visible '%s'\n", dev->name.data()); |
| // TODO(teisenbe): MakeVisibile can return errors. We should probably |
| // act on it, but the existing code being migrated does not. |
| dev->coordinator->MakeVisible(dev); |
| return fuchsia_device_manager_CoordinatorMakeVisible_reply(txn, ZX_OK); |
| } |
| |
| static zx_status_t fidl_BindDevice(void* ctx, const char* driver_path_data, size_t driver_path_size, |
| fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| fbl::StringPiece driver_path(driver_path_data, driver_path_size); |
| if (dev->coordinator->InSuspend()) { |
| log(ERROR, "devcoord: rpc: bind-device '%s' forbidden in suspend\n", dev->name.data()); |
| return fuchsia_device_manager_CoordinatorBindDevice_reply(txn, ZX_ERR_BAD_STATE); |
| } |
| log(RPC_IN, "devcoord: rpc: bind-device '%s'\n", dev->name.data()); |
| zx_status_t status = dev->coordinator->BindDevice(dev, driver_path); |
| return fuchsia_device_manager_CoordinatorBindDevice_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_GetTopologicalPath(void* ctx, fidl_txn_t* txn) { |
| char path[fuchsia_device_manager_PATH_MAX + 1]; |
| |
| auto dev = static_cast<Device*>(ctx); |
| zx_status_t status; |
| if ((status = dev->coordinator->GetTopoPath(dev, path, sizeof(path))) != ZX_OK) { |
| return fuchsia_device_manager_CoordinatorGetTopologicalPath_reply(txn, status, nullptr, 0); |
| } |
| return fuchsia_device_manager_CoordinatorGetTopologicalPath_reply(txn, ZX_OK, |
| path, strlen(path)); |
| } |
| |
| static zx_status_t fidl_LoadFirmware(void* ctx, const char* fw_path_data, size_t fw_path_size, |
| fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| |
| char fw_path[fuchsia_device_manager_PATH_MAX + 1]; |
| memcpy(fw_path, fw_path_data, fw_path_size); |
| fw_path[fw_path_size] = 0; |
| |
| zx::vmo vmo; |
| uint64_t size = 0; |
| zx_status_t status; |
| if ((status = dev->coordinator->LoadFirmware(dev, fw_path, &vmo, &size)) != ZX_OK) { |
| return fuchsia_device_manager_CoordinatorLoadFirmware_reply(txn, status, |
| ZX_HANDLE_INVALID, 0); |
| } |
| |
| return fuchsia_device_manager_CoordinatorLoadFirmware_reply(txn, ZX_OK, vmo.release(), size); |
| } |
| |
| static zx_status_t fidl_GetMetadata(void* ctx, uint32_t key, fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| |
| uint8_t data[fuchsia_device_manager_METADATA_MAX]; |
| size_t actual = 0; |
| zx_status_t status = dev->coordinator->GetMetadata(dev, key, data, sizeof(data), &actual); |
| if (status != ZX_OK) { |
| return fuchsia_device_manager_CoordinatorGetMetadata_reply(txn, status, nullptr, 0); |
| } |
| return fuchsia_device_manager_CoordinatorGetMetadata_reply(txn, status, data, actual); |
| } |
| |
| static zx_status_t fidl_GetMetadataSize(void* ctx, uint32_t key, fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| size_t size; |
| zx_status_t status = dev->coordinator->GetMetadataSize(dev, key, &size); |
| if (status != ZX_OK) { |
| return fuchsia_device_manager_CoordinatorGetMetadataSize_reply(txn, status, 0); |
| } |
| return fuchsia_device_manager_CoordinatorGetMetadataSize_reply(txn, status, size); |
| } |
| |
| static zx_status_t fidl_AddMetadata(void* ctx, uint32_t key, |
| const uint8_t* data_data, size_t data_count, fidl_txn_t* txn) { |
| static_assert(fuchsia_device_manager_METADATA_MAX <= UINT32_MAX); |
| |
| auto dev = static_cast<Device*>(ctx); |
| zx_status_t status = dev->coordinator->AddMetadata(dev, key, data_data, |
| static_cast<uint32_t>(data_count)); |
| return fuchsia_device_manager_CoordinatorAddMetadata_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_PublishMetadata(void* ctx, const char* device_path_data, |
| size_t device_path_size, uint32_t key, |
| const uint8_t* data_data, size_t data_count, |
| fidl_txn_t* txn) { |
| auto dev = static_cast<Device*>(ctx); |
| |
| char path[fuchsia_device_manager_PATH_MAX + 1]; |
| memcpy(path, device_path_data, device_path_size); |
| path[device_path_size] = 0; |
| |
| zx_status_t status = dev->coordinator->PublishMetadata(dev, path, key, data_data, |
| static_cast<uint32_t>(data_count)); |
| return fuchsia_device_manager_CoordinatorPublishMetadata_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_DmCommand(void* ctx, zx_handle_t raw_log_socket, |
| const char* command_data, size_t command_size, fidl_txn_t* txn) { |
| zx::socket log_socket(raw_log_socket); |
| |
| auto dev = static_cast<Device*>(ctx); |
| if (log_socket.is_valid()) { |
| dev->coordinator->set_dmctl_socket(std::move(log_socket)); |
| } |
| |
| zx_status_t status = dev->coordinator->HandleDmctlWrite(command_size, command_data); |
| dev->coordinator->set_dmctl_socket(zx::socket()); |
| return fuchsia_device_manager_CoordinatorDmCommand_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_DmOpenVirtcon(void* ctx, zx_handle_t raw_vc_receiver) { |
| auto dev = static_cast<Device*>(ctx); |
| return dev->coordinator->OpenVirtcon(zx::channel(raw_vc_receiver)); |
| } |
| |
| static 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.clone(ZX_VMO_CLONE_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->Mexec(std::move(kernel), std::move(bootdata)); |
| return ZX_OK; |
| } |
| |
| static 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); |
| } |
| |
| static fuchsia_device_manager_Coordinator_ops_t fidl_ops = { |
| .AddDevice = fidl_AddDevice, |
| .AddDeviceInvisible = fidl_AddDeviceInvisible, |
| .RemoveDevice = fidl_RemoveDevice, |
| .MakeVisible = fidl_MakeVisible, |
| .BindDevice = fidl_BindDevice, |
| .GetTopologicalPath = fidl_GetTopologicalPath, |
| .LoadFirmware = fidl_LoadFirmware, |
| .GetMetadata = fidl_GetMetadata, |
| .GetMetadataSize = fidl_GetMetadataSize, |
| .AddMetadata = fidl_AddMetadata, |
| .PublishMetadata = fidl_PublishMetadata, |
| .DmCommand = fidl_DmCommand, |
| .DmOpenVirtcon = fidl_DmOpenVirtcon, |
| .DmMexec = fidl_DmMexec, |
| .DirectoryWatch = fidl_DirectoryWatch, |
| }; |
| |
| zx_status_t Coordinator::HandleDeviceRead(Device* dev) { |
| uint8_t msg[8192]; |
| zx_handle_t hin[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| uint32_t msize = sizeof(msg); |
| uint32_t hcount = fbl::count_of(hin); |
| |
| if (dev->flags & DEV_CTX_DEAD) { |
| log(ERROR, "devcoord: dev %p already dead (in read)\n", dev); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| zx_status_t r; |
| if ((r = dev->hrpc.read(0, &msg, msize, &msize, hin, hcount, &hcount)) != ZX_OK) { |
| return r; |
| } |
| |
| fidl_msg_t fidl_msg = { |
| .bytes = msg, |
| .handles = hin, |
| .num_bytes = msize, |
| .num_handles = hcount, |
| }; |
| |
| if (fidl_msg.num_bytes < sizeof(fidl_message_header_t)) { |
| zx_handle_close_many(fidl_msg.handles, fidl_msg.num_handles); |
| return ZX_ERR_IO; |
| } |
| |
| auto hdr = static_cast<fidl_message_header_t*>(fidl_msg.bytes); |
| // Check if we're receiving a Coordinator request |
| { |
| FidlTxn txn(dev->hrpc, hdr->txid); |
| r = fuchsia_device_manager_Coordinator_try_dispatch(dev, txn.fidl_txn(), &fidl_msg, |
| &fidl_ops); |
| if (r != ZX_ERR_NOT_SUPPORTED) { |
| return r; |
| } |
| } |
| |
| // TODO: Check txid on the message |
| switch (hdr->ordinal) { |
| case fuchsia_device_manager_ControllerBindDriverOrdinal: |
| case fuchsia_device_manager_ControllerBindDriverGenOrdinal: { |
| const char* err_msg = nullptr; |
| r = fidl_decode_msg(&fuchsia_device_manager_ControllerBindDriverResponseTable, |
| &fidl_msg, &err_msg); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoord: rpc: bind-driver '%s' received malformed reply: %s\n", |
| dev->name.data(), err_msg); |
| return ZX_ERR_IO; |
| } |
| auto resp = reinterpret_cast<fuchsia_device_manager_ControllerBindDriverResponse*>( |
| fidl_msg.bytes); |
| if (resp->status != ZX_OK) { |
| log(ERROR, "devcoord: rpc: bind-driver '%s' status %d\n", dev->name.data(), |
| resp->status); |
| } |
| //TODO: try next driver, clear BOUND flag |
| break; |
| } |
| case fuchsia_device_manager_ControllerSuspendOrdinal: |
| case fuchsia_device_manager_ControllerSuspendGenOrdinal: { |
| const char* err_msg = nullptr; |
| r = fidl_decode_msg(&fuchsia_device_manager_ControllerSuspendResponseTable, &fidl_msg, |
| &err_msg); |
| if (r != ZX_OK) { |
| log(ERROR, "devcoord: rpc: suspend '%s' received malformed reply: %s\n", |
| dev->name.data(), err_msg); |
| return ZX_ERR_IO; |
| } |
| auto resp = reinterpret_cast<fuchsia_device_manager_ControllerSuspendResponse*>( |
| fidl_msg.bytes); |
| if (resp->status != ZX_OK) { |
| log(ERROR, "devcoord: rpc: suspend '%s' status %d\n", dev->name.data(), |
| resp->status); |
| } |
| suspend_context_.set_status(resp->status); |
| ContinueSuspend(&suspend_context_, root_resource()); |
| break; |
| } |
| default: |
| log(ERROR, "devcoord: rpc: dev '%s' received wrong unexpected reply %08x\n", |
| dev->name.data(), hdr->ordinal); |
| zx_handle_close_many(fidl_msg.handles, fidl_msg.num_handles); |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| // send message to devhost, requesting the creation of a device |
| static zx_status_t dh_create_device(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[0]) { |
| zx::vmo vmo; |
| if ((r = dev->coordinator->LibnameToVmo(dev->libname, &vmo)) != ZX_OK) { |
| return r; |
| } |
| |
| r = dh_send_create_device(dev, 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(dh, std::move(hrpc_remote), dev->protocol_id); |
| if (r != ZX_OK) { |
| return r; |
| } |
| } |
| |
| dev->wait.set_object(hrpc.get()); |
| dev->hrpc = std::move(hrpc); |
| dev->wait.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED); |
| if ((r = dev->wait.Begin(dev->coordinator->dispatcher())) != ZX_OK) { |
| return r; |
| } |
| dev->host = dh; |
| dh->AddRef(); |
| dh->devices().push_back(dev); |
| return ZX_OK; |
| } |
| |
| static zx_status_t dc_create_proxy(Coordinator* coordinator, Device* parent) { |
| if (parent->proxy != nullptr) { |
| return ZX_OK; |
| } |
| |
| auto dev = fbl::make_unique<Device>(coordinator); |
| if (dev == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| dev->name = parent->name; |
| dev->libname = parent->libname; |
| // non-immortal devices, use foo.proxy.so for |
| // their proxy devices instead of foo.so |
| if (!(parent->flags & DEV_CTX_IMMORTAL)) { |
| const char* begin = dev->libname.data(); |
| const char* end = strstr(begin, ".so"); |
| fbl::StringPiece prefix(begin, end == nullptr ? dev->libname.size() : end - begin); |
| fbl::AllocChecker ac; |
| dev->libname = fbl::String::Concat({prefix, ".proxy.so"}, &ac); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| } |
| |
| dev->flags = DEV_CTX_PROXY; |
| dev->protocol_id = parent->protocol_id; |
| dev->parent = parent; |
| dev->AddRef(); |
| parent->proxy = dev.get(); |
| parent->AddRef(); |
| log(DEVLC, "devcoord: dev %p name='%s' ++ref=%d (proxy)\n", |
| parent, parent->name.data(), parent->refcount_); |
| // TODO(teisenbe/kulakowski): This should go away once we switch to refptrs |
| // here |
| __UNUSED auto ptr = dev.release(); |
| return ZX_OK; |
| } |
| |
| // send message to devhost, requesting the binding of a driver to a device |
| static zx_status_t dh_bind_driver(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, libname, std::move(vmo)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| dev->flags |= DEV_CTX_BOUND; |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::PrepareProxy(Device* dev) { |
| if (dev->flags & DEV_CTX_PROXY) { |
| log(ERROR, "devcoord: cannot proxy a proxy: %s\n", dev->name.data()); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // 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 ((r = dc_create_proxy(this, dev)) < 0) { |
| 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; |
| // May be either a VMO or a channel. |
| zx::handle h1; |
| |
| // the immortal root devices do not provide proxy rpc |
| bool need_proxy_rpc = !(dev->flags & DEV_CTX_IMMORTAL); |
| |
| if (need_proxy_rpc) { |
| // create rpc channel for proxy device to talk to the busdev it proxys |
| zx::channel c1; |
| if ((r = zx::channel::create(0, &h0, &c1)) < 0) { |
| log(ERROR, "devcoord: cannot create proxy rpc channel: %d\n", r); |
| return r; |
| } |
| h1 = std::move(c1); |
| } else if (dev == &sys_device_) { |
| // pass bootdata VMO handle to sys device |
| h1 = std::move(bootdata_vmo_); |
| } |
| if ((r = NewDevhost(devhostname, dev->host, &dev->proxy->host)) < 0) { |
| log(ERROR, "devcoord: dc_new_devhost: %d\n", r); |
| return r; |
| } |
| if ((r = dh_create_device(dev->proxy, dev->proxy->host, arg1, std::move(h1))) < 0) { |
| log(ERROR, "devcoord: dh_create_device: %d\n", r); |
| return r; |
| } |
| if (need_proxy_rpc) { |
| if ((r = dh_send_connect_proxy(dev, std::move(h0))) < 0) { |
| log(ERROR, "devcoord: dh_send_connect_proxy: %d\n", r); |
| } |
| } |
| if (dev->client_remote.is_valid()) { |
| if ((r = devfs_connect(dev->proxy, std::move(dev->client_remote))) != ZX_OK) { |
| log(ERROR, "devcoord: devfs_connnect: %d\n", r); |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Coordinator::AttemptBind(const Driver* drv, 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, "devcoord: 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)) < 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(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->client_remote.is_valid() && !(dev->flags & DEV_CTX_MUST_ISOLATE)) { |
| zx_status_t r; |
| if ((r = devfs_connect(dev, std::move(dev->client_remote))) != ZX_OK) { |
| log(ERROR, "devcoord: devfs_connnect: %d\n", r); |
| } |
| } |
| for (auto& drv : drivers_) { |
| if (dc_is_bindable(&drv, dev->protocol_id, |
| dev->props.get(), dev->prop_count, true)) { |
| log(SPEW, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv.name.data(), dev->name.data()); |
| |
| AttemptBind(&drv, dev); |
| if (!(dev->flags & DEV_CTX_MULTI_BIND)) { |
| break; |
| } |
| } |
| } |
| } |
| |
| static void append_suspend_list(SuspendContext* ctx, Devhost* dh) { |
| // suspend order is children first |
| for (auto& child : dh->children()) { |
| ctx->devhosts().push_front(&child); |
| } |
| for (auto& child : dh->children()) { |
| append_suspend_list(ctx, &child); |
| } |
| } |
| |
| // Returns the devhost at the front of the queue. |
| void Coordinator::BuildSuspendList() { |
| auto& devhosts = suspend_context_.devhosts(); |
| |
| // sys_device must suspend last as on x86 it invokes |
| // ACPI S-state transition |
| devhosts.push_front(sys_device_.proxy->host); |
| append_suspend_list(&suspend_context_, sys_device_.proxy->host); |
| |
| devhosts.push_front(root_device_.proxy->host); |
| append_suspend_list(&suspend_context_, root_device_.proxy->host); |
| |
| devhosts.push_front(misc_device_.proxy->host); |
| append_suspend_list(&suspend_context_, misc_device_.proxy->host); |
| |
| // test devices do not (yet) participate in suspend |
| |
| suspend_context_.set_dh(&devhosts.front()); |
| } |
| |
| static int suspend_timeout_thread(void* arg) { |
| // 10 seconds |
| zx_nanosleep(zx_deadline_after(ZX_SEC(10))); |
| |
| auto ctx = static_cast<SuspendContext*>(arg); |
| auto coordinator = ctx->coordinator(); |
| if (coordinator->suspend_debug()) { |
| if (ctx->flags() == SuspendContext::Flags::kRunning) { |
| return 0; // success |
| } |
| log(ERROR, "devcoord: 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) { |
| // these top level devices should all have proxies. if not, |
| // the system hasn't fully initialized yet and cannot go to |
| // suspend. |
| if (!sys_device_.proxy || !root_device_.proxy || !misc_device_.proxy) { |
| return; |
| } |
| if (suspend_context_.flags() == SuspendContext::Flags::kSuspend) { |
| return; |
| } |
| // Move the socket in to prevent the rpc handler from closing the handle. |
| suspend_context_ = std::move(ctx); |
| BuildSuspendList(); |
| |
| if (suspend_fallback_ || suspend_debug_) { |
| thrd_t t; |
| int ret = thrd_create_with_name(&t, suspend_timeout_thread, &suspend_context_, |
| "devcoord-suspend-timeout"); |
| if (ret != thrd_success) { |
| log(ERROR, "devcoord: can't create suspend timeout thread\n"); |
| } |
| } |
| |
| process_suspend_list(&suspend_context_); |
| } |
| |
| void Coordinator::Suspend(uint32_t flags) { |
| Suspend(SuspendContext(this, SuspendContext::Flags::kSuspend, flags, std::move(dmctl_socket_))); |
| } |
| |
| void Coordinator::Mexec(zx::vmo kernel, zx::vmo bootdata) { |
| Suspend(SuspendContext(this, SuspendContext::Flags::kSuspend, DEVICE_SUSPEND_FLAG_MEXEC, |
| zx::socket(), std::move(kernel), std::move(bootdata))); |
| } |
| |
| // device binding program that pure (parentless) |
| // misc devices use to get published in the misc devhost |
| static struct zx_bind_inst misc_device_binding = |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT); |
| |
| static bool is_misc_driver(Driver* drv) { |
| return (drv->binding_size == sizeof(misc_device_binding)) && |
| (memcmp(&misc_device_binding, drv->binding.get(), sizeof(misc_device_binding)) == 0); |
| } |
| |
| // device binding program that pure (parentless) |
| // test devices use to get published in the test devhost |
| static struct zx_bind_inst test_device_binding = |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_PARENT); |
| |
| static bool is_test_driver(Driver* drv) { |
| return (drv->binding_size == sizeof(test_device_binding)) && |
| (memcmp(&test_device_binding, drv->binding.get(), sizeof(test_device_binding)) == 0); |
| } |
| |
| |
| // device binding program that special root-level |
| // devices use to get published in the root devhost |
| static struct zx_bind_inst root_device_binding = |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_ROOT); |
| |
| static bool is_root_driver(Driver* drv) { |
| return (drv->binding_size == sizeof(root_device_binding)) && |
| (memcmp(&root_device_binding, drv->binding.get(), sizeof(root_device_binding)) == 0); |
| } |
| |
| 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); |
| BindDriver(drv); |
| }); |
| } |
| |
| // 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; |
| } |
| 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, "devmgr: adding system driver '%s' '%s'\n", driver->name.data(), |
| driver->libname.data()); |
| if (load_vmo(driver->libname.data(), &driver->dso_vmo)) { |
| log(ERROR, "devmgr: 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()); |
| } |
| } |
| |
| // 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). |
| void Coordinator::BindDriver(Driver* drv) { |
| if (running_) { |
| printf("devcoord: driver '%s' added\n", drv->name.data()); |
| } |
| if (is_root_driver(drv)) { |
| AttemptBind(drv, &root_device_); |
| } else if (is_misc_driver(drv)) { |
| AttemptBind(drv, &misc_device_); |
| } else if (is_test_driver(drv)) { |
| AttemptBind(drv, &test_device_); |
| } else if (running_) { |
| for (auto& dev : devices_) { |
| if (dev.flags & (DEV_CTX_BOUND | DEV_CTX_DEAD | |
| DEV_CTX_ZOMBIE | DEV_CTX_INVISIBLE)) { |
| // if device is already bound or being destroyed or invisible, skip it |
| continue; |
| } |
| if (dc_is_bindable(drv, dev.protocol_id, |
| dev.props.get(), dev.prop_count, true)) { |
| log(INFO, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv->name.data(), dev.name.data()); |
| |
| AttemptBind(drv, &dev); |
| } |
| } |
| } |
| } |
| |
| static int system_driver_loader(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; |
| } |
| |
| void Coordinator::ScanSystemDrivers() { |
| if (system_loaded_) { |
| return; |
| } |
| 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; |
| int ret = thrd_create_with_name(&t, system_driver_loader, this, "system-driver-loader"); |
| if (ret == thrd_success) { |
| thrd_detach(t); |
| } |
| } |
| |
| void Coordinator::BindSystemDrivers() { |
| Driver* drv; |
| // Bind system drivers. |
| while ((drv = system_drivers_.pop_front()) != nullptr) { |
| drivers_.push_back(drv); |
| BindDriver(drv); |
| } |
| // Bind remaining fallback drivers. |
| while ((drv = fallback_drivers_.pop_front()) != nullptr) { |
| printf("devcoord: fallback driver '%s' is available\n", drv->name.data()); |
| drivers_.push_back(drv); |
| BindDriver(drv); |
| } |
| } |
| |
| void Coordinator::BindDrivers() { |
| for (Driver& drv : drivers_) { |
| BindDriver(&drv); |
| } |
| } |
| |
| void Coordinator::UseFallbackDrivers() { |
| drivers_.splice(drivers_.end(), fallback_drivers_); |
| } |
| |
| void coordinator_setup(Coordinator* coordinator, DevmgrArgs args) { |
| log(INFO, "devmgr: coordinator_setup()\n"); |
| |
| // Set up the default values for our arguments if they weren't given. |
| if (args.driver_search_paths.size() == 0) { |
| args.driver_search_paths.push_back("/boot/driver"); |
| } |
| if (args.sys_device_driver == nullptr) { |
| // x86 platforms use acpi as the system device |
| // all other platforms use the platform bus |
| #if defined(__x86_64__) |
| args.sys_device_driver = "/boot/driver/bus-acpi.so"; |
| #else |
| args.sys_device_driver = "/boot/driver/platform-bus.so"; |
| #endif |
| } |
| |
| if (getenv_bool("devmgr.verbose", false)) { |
| log_flags |= LOG_ALL; |
| } |
| |
| coordinator->set_suspend_fallback(getenv_bool("devmgr.suspend-timeout-fallback", false)); |
| coordinator->set_suspend_debug(getenv_bool("devmgr.suspend-timeout-debug", false)); |
| |
| zx_status_t status = coordinator->InitializeCoreDevices(); |
| if (status != ZX_OK) { |
| log(ERROR, "devmgr: failed to initialize core devices\n"); |
| return; |
| } |
| |
| devfs_publish(&coordinator->root_device(), &coordinator->misc_device()); |
| devfs_publish(&coordinator->root_device(), &coordinator->sys_device()); |
| devfs_publish(&coordinator->root_device(), &coordinator->test_device()); |
| |
| for (const char* path : args.driver_search_paths) { |
| find_loadable_drivers(path, fit::bind_member(coordinator, &Coordinator::DriverAddedInit)); |
| } |
| for (const char* driver : args.load_drivers) { |
| load_driver(driver, fit::bind_member(coordinator, &Coordinator::DriverAddedInit)); |
| } |
| |
| // Special case early handling for the ramdisk boot |
| // path where /system is present before the coordinator |
| // starts. This avoids breaking the "priority hack" and |
| // can be removed once the real driver priority system |
| // exists. |
| if (coordinator->system_available()) { |
| coordinator->ScanSystemDrivers(); |
| } |
| |
| coordinator->sys_device().libname = args.sys_device_driver; |
| coordinator->PrepareProxy(&coordinator->sys_device()); |
| coordinator->PrepareProxy(&coordinator->test_device()); |
| |
| if (coordinator->require_system() && !coordinator->system_loaded()) { |
| printf("devcoord: full system required, ignoring fallback drivers until /system is loaded\n"); |
| } else { |
| coordinator->UseFallbackDrivers(); |
| } |
| |
| // Initial bind attempt for drivers enumerated at startup. |
| coordinator->BindDrivers(); |
| |
| coordinator->set_running(true); |
| } |
| |
| } // namespace devmgr |