| // 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 <ctype.h> |
| #include <fcntl.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <ddk/driver.h> |
| #include <driver-info/driver-info.h> |
| #include <launchpad/launchpad.h> |
| #include <zircon/assert.h> |
| #include <zircon/ktrace.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/policy.h> |
| #include <zircon/device/dmctl.h> |
| #include <zircon/boot/bootdata.h> |
| #include <fdio/io.h> |
| |
| #include "devcoordinator.h" |
| #include "devmgr.h" |
| #include "log.h" |
| #include "memfs-private.h" |
| |
| #include <pretty/hexdump.h> |
| |
| extern zx_handle_t virtcon_open; |
| |
| uint32_t log_flags = LOG_ERROR | LOG_INFO; |
| |
| bool dc_asan_drivers = false; |
| |
| static void dc_dump_state(void); |
| static void dc_dump_devprops(void); |
| static void dc_dump_drivers(void); |
| |
| static zx_status_t dh_suspend(device_t* dev, uint32_t flags); |
| |
| static device_t sys_device; |
| |
| static zx_handle_t dmctl_socket; |
| |
| static void dmprintf(const char* fmt, ...) { |
| if (dmctl_socket == ZX_HANDLE_INVALID) { |
| return; |
| } |
| char buf[1024]; |
| va_list ap; |
| va_start(ap, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| size_t actual; |
| if (zx_socket_write(dmctl_socket, 0, buf, strlen(buf), &actual) < 0) { |
| zx_handle_close(dmctl_socket); |
| dmctl_socket = ZX_HANDLE_INVALID; |
| } |
| } |
| |
| static zx_status_t handle_dmctl_write(size_t len, const char* cmd) { |
| if (len == 4) { |
| if (!memcmp(cmd, "dump", 4)) { |
| dc_dump_state(); |
| 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" |
| "reboot - reboot the system\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 == 6) && !memcmp(cmd, "reboot", 6)) { |
| devmgr_vfs_exit(); |
| dh_suspend(&sys_device, DEVICE_SUSPEND_FLAG_REBOOT); |
| zx_debug_send_command(get_root_resource(), "reboot", sizeof("reboot")); |
| return ZX_OK; |
| } |
| if ((len == 7) && !memcmp(cmd, "drivers", 7)) { |
| dc_dump_drivers(); |
| return ZX_OK; |
| } |
| if (len == 8) { |
| if (!memcmp(cmd, "poweroff", 8) || !memcmp(cmd, "shutdown", 8)) { |
| devmgr_vfs_exit(); |
| dh_suspend(&sys_device, DEVICE_SUSPEND_FLAG_POWEROFF); |
| zx_debug_send_command(get_root_resource(), "poweroff", sizeof("poweroff")); |
| return ZX_OK; |
| } |
| if (!memcmp(cmd, "ktraceon", 8)) { |
| zx_ktrace_control(get_root_resource(), KTRACE_ACTION_START, KTRACE_GRP_ALL, NULL); |
| return ZX_OK; |
| } |
| if (!memcmp(cmd, "devprops", 8)) { |
| dc_dump_devprops(); |
| return ZX_OK; |
| } |
| } |
| if ((len == 9) && (!memcmp(cmd, "ktraceoff", 9))) { |
| zx_ktrace_control(get_root_resource(), KTRACE_ACTION_STOP, 0, NULL); |
| zx_ktrace_control(get_root_resource(), KTRACE_ACTION_REWIND, 0, NULL); |
| return ZX_OK; |
| } |
| if ((len > 12) && !memcmp(cmd, "kerneldebug ", 12)) { |
| return zx_debug_send_command(get_root_resource(), cmd + 12, len - 12); |
| } |
| 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); |
| return ZX_OK; |
| } |
| dmprintf("unknown command\n"); |
| log(ERROR, "dmctl: unknown command '%.*s'\n", (int) len, cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| //TODO: these are copied from devhost.h |
| #define ID_HJOBROOT 4 |
| zx_handle_t get_sysinfo_job_root(void); |
| |
| |
| static zx_status_t dc_handle_device(port_handler_t* ph, zx_signals_t signals, uint32_t evt); |
| static zx_status_t dc_attempt_bind(driver_t* drv, device_t* dev); |
| |
| static bool dc_running; |
| |
| static zx_handle_t dc_watch_channel; |
| |
| static zx_handle_t devhost_job; |
| port_t dc_port; |
| |
| // All Drivers |
| static list_node_t list_drivers = LIST_INITIAL_VALUE(list_drivers); |
| |
| // Drivers to add to All Drivers |
| static list_node_t list_drivers_new = LIST_INITIAL_VALUE(list_drivers_new); |
| |
| // Drivers to try last |
| static list_node_t list_drivers_fallback = LIST_INITIAL_VALUE(list_drivers_fallback); |
| |
| // All Devices (excluding static immortal devices) |
| static list_node_t list_devices = LIST_INITIAL_VALUE(list_devices); |
| |
| // Composite devices that are pending to be made visible |
| static list_node_t list_devices_pending = LIST_INITIAL_VALUE(list_devices_pending); |
| |
| static driver_t* libname_to_driver(const char* libname) { |
| driver_t* drv; |
| list_for_every_entry(&list_drivers, drv, driver_t, node) { |
| if (!strcmp(libname, drv->libname)) { |
| return drv; |
| } |
| } |
| return NULL; |
| } |
| |
| static zx_status_t libname_to_vmo(const char* libname, zx_handle_t* out) { |
| driver_t* drv = libname_to_driver(libname); |
| if (drv == NULL) { |
| log(ERROR, "devcoord: cannot find driver '%s'\n", libname); |
| return ZX_ERR_NOT_FOUND; |
| } |
| int fd = open(libname, O_RDONLY); |
| if (fd < 0) { |
| log(ERROR, "devcoord: cannot open driver '%s'\n", libname); |
| return ZX_ERR_IO; |
| } |
| zx_status_t r = fdio_get_vmo(fd, out); |
| close(fd); |
| if (r < 0) { |
| log(ERROR, "devcoord: cannot get driver vmo '%s'\n", libname); |
| } |
| return r; |
| } |
| |
| static device_t root_device = { |
| .flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND, |
| .protocol_id = ZX_PROTOCOL_ROOT, |
| .name = "root", |
| .libname = "", |
| .args = "root,", |
| .children = LIST_INITIAL_VALUE(root_device.children), |
| .pending = LIST_INITIAL_VALUE(root_device.pending), |
| .refcount = 1, |
| }; |
| |
| static device_t misc_device = { |
| .parent = &root_device, |
| .flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND, |
| .protocol_id = ZX_PROTOCOL_MISC_PARENT, |
| .name = "misc", |
| .libname = "", |
| .args = "misc,", |
| .children = LIST_INITIAL_VALUE(misc_device.children), |
| .pending = LIST_INITIAL_VALUE(misc_device.pending), |
| .refcount = 1, |
| }; |
| |
| static device_t sys_device = { |
| .parent = &root_device, |
| .flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE, |
| .name = "sys", |
| .libname = "", |
| .args = "sys,", |
| .children = LIST_INITIAL_VALUE(sys_device.children), |
| .pending = LIST_INITIAL_VALUE(sys_device.pending), |
| .refcount = 1, |
| }; |
| |
| zx_status_t devmgr_set_platform_id(zx_handle_t vmo, zx_off_t offset, size_t length) { |
| bootdata_platform_id_t platform_id; |
| size_t actual; |
| |
| zx_status_t status = zx_vmo_read(vmo, &platform_id, offset, sizeof(platform_id), &actual); |
| if ((status < 0) || (actual != sizeof(platform_id))) { |
| return status; |
| } |
| |
| char buffer[100]; |
| snprintf(buffer, sizeof(buffer), "sys,vid=%u,pid=%u,board=%s,", platform_id.vid, platform_id.pid, |
| platform_id.board_name); |
| buffer[countof(buffer) - 1] = 0; |
| sys_device.args = strdup(buffer); |
| return ZX_OK; |
| } |
| |
| static void dc_dump_device(device_t* dev, size_t indent) { |
| 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, extra); |
| } else { |
| dmprintf("%*s%c%s%c pid=%zu%s %s\n", |
| (int) (indent * 3), "", |
| dev->flags & DEV_CTX_PROXY ? '<' : '[', |
| dev->name, |
| dev->flags & DEV_CTX_PROXY ? '>' : ']', |
| pid, extra, |
| dev->libname ? dev->libname : ""); |
| } |
| device_t* child; |
| if (dev->proxy) { |
| indent++; |
| dc_dump_device(dev->proxy, indent); |
| } |
| list_for_every_entry(&dev->children, child, device_t, node) { |
| dc_dump_device(child, indent + 1); |
| } |
| } |
| |
| static void dc_dump_state(void) { |
| dc_dump_device(&root_device, 0); |
| dc_dump_device(&misc_device, 1); |
| dc_dump_device(&sys_device, 1); |
| } |
| |
| static void dc_dump_device_props(device_t* dev) { |
| if (dev->host) { |
| dmprintf("Name [%s]%s%s%s\n", |
| dev->name, |
| dev->libname ? " Driver [" : "", |
| dev->libname ? dev->libname : "", |
| dev->libname ? "]" : ""); |
| 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"); |
| } |
| |
| device_t* child; |
| if (dev->proxy) { |
| dc_dump_device_props(dev->proxy); |
| } |
| list_for_every_entry(&dev->children, child, device_t, node) { |
| dc_dump_device_props(child); |
| } |
| } |
| |
| static void dc_dump_devprops(void) { |
| dc_dump_device_props(&root_device); |
| dc_dump_device_props(&misc_device); |
| dc_dump_device_props(&sys_device); |
| } |
| |
| static void dc_dump_drivers(void) { |
| driver_t* drv; |
| bool first = true; |
| list_for_every_entry(&list_drivers, drv, driver_t, node) { |
| dmprintf("%sName : %s\n", first ? "" : "\n", drv->name); |
| dmprintf("Driver : %s\n", drv->libname ? drv->libname : "(null)"); |
| dmprintf("Flags : 0x%08x\n", drv->flags); |
| if (drv->binding_size) { |
| char line[256]; |
| uint32_t count = drv->binding_size / 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 void dc_handle_new_device(device_t* dev); |
| static void dc_handle_new_composite_device(device_t* dev); |
| static void dc_handle_new_driver(void); |
| |
| #define WORK_IDLE 0 |
| #define WORK_DEVICE_ADDED 1 |
| #define WORK_DRIVER_ADDED 2 |
| #define WORK_COMPOSITE_DEVICE_ADDED 3 |
| |
| static list_node_t list_pending_work = LIST_INITIAL_VALUE(list_pending_work); |
| static list_node_t list_unbound_devices = LIST_INITIAL_VALUE(list_unbound_devices); |
| |
| static void queue_work(work_t* work, uint32_t op, uint32_t arg) { |
| ZX_ASSERT(work->op == WORK_IDLE); |
| work->op = op; |
| work->arg = arg; |
| list_add_tail(&list_pending_work, &work->node); |
| } |
| |
| static void cancel_work(work_t* work) { |
| if (work->op != WORK_IDLE) { |
| list_delete(&work->node); |
| work->op = WORK_IDLE; |
| } |
| } |
| |
| static void process_work(work_t* work) { |
| uint32_t op = work->op; |
| work->op = WORK_IDLE; |
| |
| switch (op) { |
| case WORK_DEVICE_ADDED: { |
| device_t* dev = containerof(work, device_t, work); |
| dc_handle_new_device(dev); |
| // check if the new device satisfied any pending composite devices |
| device_t* temp; |
| list_for_every_entry_safe(&list_devices_pending, dev, temp, device_t, anode) { |
| dc_handle_new_composite_device(dev); |
| } |
| break; |
| } |
| case WORK_COMPOSITE_DEVICE_ADDED: { |
| device_t* dev = containerof(work, device_t, work); |
| dc_handle_new_composite_device(dev); |
| } |
| case WORK_DRIVER_ADDED: { |
| dc_handle_new_driver(); |
| break; |
| } |
| default: |
| log(ERROR, "devcoord: unknown work: op=%u\n", op); |
| } |
| } |
| |
| static const char* get_devhost_bin(void) { |
| // 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 (dc_asan_drivers) |
| return "/boot/bin/devhost.asan"; |
| return "/boot/bin/devhost"; |
| } |
| |
| zx_handle_t get_service_root(void); |
| |
| static zx_status_t dc_get_topo_path(device_t* dev, char* out, size_t max) { |
| char tmp[max]; |
| char* path = tmp + max - 1; |
| *path = 0; |
| size_t total = 1; |
| |
| while (dev != NULL) { |
| if (dev->flags & DEV_CTX_PROXY) { |
| dev = dev->parent; |
| } |
| const char* name; |
| |
| if (dev->parent) { |
| name = dev->name; |
| } else if (!strcmp(misc_device.name, dev->name)) { |
| name = "dev/misc"; |
| } else if (!strcmp(sys_device.name, dev->name)) { |
| name = "dev/sys"; |
| } 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; |
| } |
| |
| //TODO: use a better device identifier |
| static zx_status_t dc_notify(device_t* dev, uint32_t op) { |
| if (dc_watch_channel == ZX_HANDLE_INVALID) { |
| return ZX_ERR_BAD_STATE; |
| } |
| zx_status_t r; |
| if (op == DEVMGR_OP_DEVICE_ADDED) { |
| size_t propslen = sizeof(zx_device_prop_t) * dev->prop_count; |
| size_t len = sizeof(devmgr_event_t) + propslen; |
| char msg[len + DC_PATH_MAX]; |
| devmgr_event_t* evt = (void*) msg; |
| memset(evt, 0, sizeof(devmgr_event_t)); |
| memcpy(msg + sizeof(devmgr_event_t), dev->props, propslen); |
| if (dc_get_topo_path(dev, msg + len, DC_PATH_MAX) < 0) { |
| return ZX_OK; |
| } |
| size_t pathlen = strlen(msg + len); |
| len += pathlen; |
| evt->opcode = op; |
| if (dev->flags & DEV_CTX_BOUND) { |
| evt->flags |= DEVMGR_FLAGS_BOUND; |
| } |
| evt->id = (uintptr_t) dev; |
| evt->u.add.protocol_id = dev->protocol_id; |
| evt->u.add.props_len = propslen; |
| evt->u.add.path_len = pathlen; |
| r = zx_channel_write(dc_watch_channel, 0, msg, len, NULL, 0); |
| } else { |
| devmgr_event_t evt; |
| memset(&evt, 0, sizeof(evt)); |
| evt.opcode = op; |
| if (dev->flags & DEV_CTX_BOUND) { |
| evt.flags |= DEVMGR_FLAGS_BOUND; |
| } |
| evt.id = (uintptr_t) dev; |
| r = zx_channel_write(dc_watch_channel, 0, &evt, sizeof(evt), NULL, 0); |
| } |
| if (r < 0) { |
| zx_handle_close(dc_watch_channel); |
| dc_watch_channel = ZX_HANDLE_INVALID; |
| } |
| return r; |
| } |
| |
| static void dc_watch(zx_handle_t h) { |
| if (dc_watch_channel != ZX_HANDLE_INVALID) { |
| zx_handle_close(dc_watch_channel); |
| } |
| dc_watch_channel = h; |
| device_t* dev; |
| list_for_every_entry(&list_devices, dev, device_t, anode) { |
| if (dev->flags & (DEV_CTX_DEAD | DEV_CTX_ZOMBIE)) { |
| // if device is dead, ignore it |
| continue; |
| } |
| if (dc_notify(dev, DEVMGR_OP_DEVICE_ADDED) < 0) { |
| break; |
| } |
| } |
| } |
| |
| static zx_status_t dc_launch_devhost(devhost_t* host, |
| const char* name, zx_handle_t hrpc) { |
| const char* devhost_bin = get_devhost_bin(); |
| |
| launchpad_t* lp; |
| launchpad_create_with_jobs(devhost_job, 0, name, &lp); |
| launchpad_load_from_file(lp, devhost_bin); |
| launchpad_set_args(lp, 1, &devhost_bin); |
| |
| launchpad_add_handle(lp, hrpc, PA_HND(PA_USER0, 0)); |
| |
| zx_handle_t h; |
| //TODO: limit root resource to root devhost only |
| zx_handle_duplicate(get_root_resource(), ZX_RIGHT_SAME_RIGHTS, &h); |
| launchpad_add_handle(lp, h, PA_HND(PA_RESOURCE, 0)); |
| |
| // Inherit devmgr's environment (including kernel cmdline) |
| launchpad_clone(lp, LP_CLONE_ENVIRON); |
| |
| const char* nametable[2] = { "/", "/svc", }; |
| size_t name_count = 0; |
| |
| //TODO: eventually devhosts should not have vfs access |
| launchpad_add_handle(lp, fs_root_clone(), |
| PA_HND(PA_NS_DIR, name_count++)); |
| |
| //TODO: constrain to /svc/device |
| if ((h = svc_root_clone()) != ZX_HANDLE_INVALID) { |
| launchpad_add_handle(lp, h, PA_HND(PA_NS_DIR, name_count++)); |
| } |
| |
| launchpad_set_nametable(lp, name_count, nametable); |
| |
| //TODO: limit root job access to root devhost only |
| launchpad_add_handle(lp, get_sysinfo_job_root(), |
| PA_HND(PA_USER0, ID_HJOBROOT)); |
| |
| const char* errmsg; |
| zx_status_t status = launchpad_go(lp, &host->proc, &errmsg); |
| if (status < 0) { |
| log(ERROR, "devcoord: launch devhost '%s': failed: %d: %s\n", |
| name, status, errmsg); |
| return status; |
| } |
| zx_info_handle_basic_t info; |
| if (zx_object_get_info(host->proc, ZX_INFO_HANDLE_BASIC, &info, |
| sizeof(info), NULL, NULL) == ZX_OK) { |
| host->koid = info.koid; |
| } |
| log(INFO, "devcoord: launch devhost '%s': pid=%zu\n", |
| name, host->koid); |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t dc_new_devhost(const char* name, devhost_t** out) { |
| devhost_t* dh = calloc(1, sizeof(devhost_t)); |
| if (dh == NULL) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_handle_t hrpc; |
| zx_status_t r; |
| if ((r = zx_channel_create(0, &hrpc, &dh->hrpc)) < 0) { |
| free(dh); |
| return r; |
| } |
| |
| if ((r = dc_launch_devhost(dh, name, hrpc)) < 0) { |
| zx_handle_close(dh->hrpc); |
| free(dh); |
| return r; |
| } |
| |
| list_initialize(&dh->devices); |
| |
| *out = dh; |
| return ZX_OK; |
| } |
| |
| static void dc_release_devhost(devhost_t* dh) { |
| log(DEVLC, "devcoord: release host %p\n", dh); |
| dh->refcount--; |
| if (dh->refcount > 0) { |
| return; |
| } |
| log(INFO, "devcoord: destroy host %p\n", dh); |
| zx_handle_close(dh->hrpc); |
| zx_task_kill(dh->proc); |
| zx_handle_close(dh->proc); |
| free(dh); |
| } |
| |
| // called when device children or proxys are removed |
| static void dc_release_device(device_t* dev) { |
| log(DEVLC, "devcoord: release dev %p name='%s' ref=%d\n", dev, dev->name, dev->refcount); |
| |
| dev->refcount--; |
| if (dev->refcount > 0) { |
| 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); |
| |
| devfs_unpublish(dev); |
| |
| if (dev->hrpc != ZX_HANDLE_INVALID) { |
| zx_handle_close(dev->hrpc); |
| dev->hrpc = ZX_HANDLE_INVALID; |
| dev->ph.handle = ZX_HANDLE_INVALID; |
| } |
| dev->host = NULL; |
| |
| cancel_work(&dev->work); |
| |
| //TODO: cancel any pending rpc responses |
| free(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. |
| static zx_status_t dc_add_device(device_t* parent, zx_handle_t hrpc, |
| dc_msg_t* msg, const char* name, |
| const char* args, const void* data, |
| bool invisible) { |
| uint32_t dep_count = 0; |
| const uint8_t* src = data; |
| if (msg->data2len > 0) { |
| src = data + msg->datalen; |
| memcpy(&dep_count, src, sizeof(uint32_t)); |
| } |
| |
| device_t* dev; |
| // allocate device struct, followed by space for props, followed by space for |
| // dependency props, followed by space for bus arguments, followed by space for the name |
| size_t sz = sizeof(*dev) + msg->datalen + |
| msg->data2len + dep_count * sizeof(zx_device_prop_t*) + |
| msg->argslen + msg->namelen + 2; |
| if ((dev = calloc(1, sz)) == NULL) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| list_initialize(&dev->children); |
| list_initialize(&dev->pending); |
| dev->hrpc = hrpc; |
| dev->protocol_id = msg->protocol_id; |
| |
| printf("==== device add ====\n"); |
| printf("msg at %p datalen %u data2len %u argslen %u namelen %u\n", data, msg->datalen, |
| msg->data2len, msg->argslen, msg->namelen); |
| if (data) { |
| hexdump8(data, msg->datalen + msg->data2len + msg->argslen + msg->namelen); |
| } |
| |
| uint32_t len; |
| if (msg->datalen > 0) { |
| dev->prop_count = msg->datalen / sizeof(zx_device_prop_t); |
| len = dev->prop_count * sizeof(zx_device_prop_t); |
| src = data; |
| memcpy(dev->props, src, len); |
| } else { |
| dev->prop_count = 0; |
| } |
| |
| printf("prop count %u\n", dev->prop_count); |
| printf("props at %p\n", dev->props); |
| hexdump8(dev->props, dev->prop_count * sizeof(zx_device_prop_t)); |
| |
| uint32_t i; |
| zx_binding_t* deps = (zx_binding_t*)(dev->props + dev->prop_count); |
| uint8_t* dst = (uint8_t*)(deps + dep_count); |
| if (msg->data2len > 0) { |
| dev->dep_count = dep_count; |
| dev->deps = deps; |
| src = data + msg->datalen + sizeof(uint32_t); |
| for (i = 0; i < dep_count; i++) { |
| len = sizeof(uint32_t); |
| memcpy(&deps->bindcount, src, len); |
| src += len; |
| deps->bindings = (zx_bind_inst_t*)dst; |
| len = deps->bindcount * sizeof(zx_bind_inst_t); |
| memcpy(dst, src, len); |
| dst += len; |
| src += len; |
| deps += 1; |
| } |
| } else { |
| dev->dep_count = 0; |
| } |
| |
| printf("dep_count %u\n", dev->dep_count); |
| printf("deps at %p\n", dev->deps); |
| for (i = 0; i < dev->dep_count; i++) { |
| printf("%02u: bindcount=%u\n", i, dev->deps[i].bindcount); |
| hexdump8(dev->deps[i].bindings, dev->deps[i].bindcount * sizeof(zx_bind_inst_t)); |
| } |
| |
| bool composite = (dev->dep_count > 0); |
| |
| char* text = (char*)dst; |
| memcpy(text, args, msg->argslen + 1); |
| dev->args = (char*)text; |
| |
| printf("args at %p '%s' (src %p '%s')\n", dev->args, dev->args, args, args); |
| |
| text += msg->argslen + 1; |
| memcpy(text, name, msg->namelen + 1); |
| |
| char* text2 = strchr(text, ','); |
| if (text2 != NULL) { |
| *text2++ = 0; |
| dev->name = text2; |
| dev->libname = text; |
| } else { |
| dev->name = text; |
| dev->libname = ""; |
| } |
| |
| printf("name at %p '%s' (src %p) libname '%s'\n", dev->name, dev->name, name, dev->libname); |
| |
| if (strlen(dev->name) > ZX_DEVICE_NAME_MAX) { |
| free(dev); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // If we have bus device args we are, |
| // by definition, a bus device. |
| if (args[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; |
| |
| zx_status_t r; |
| if ((r = devfs_publish(parent, dev)) < 0) { |
| free(dev); |
| return r; |
| } |
| |
| dev->ph.handle = hrpc; |
| dev->ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| dev->ph.func = dc_handle_device; |
| if ((r = port_wait(&dc_port, &dev->ph)) < 0) { |
| devfs_unpublish(dev); |
| free(dev); |
| return r; |
| } |
| |
| if (dev->host) { |
| //TODO host == NULL should be impossible |
| dev->host->refcount++; |
| list_add_tail(&dev->host->devices, &dev->dhnode); |
| } |
| dev->refcount = 1; |
| list_add_tail(&parent->children, &dev->node); |
| parent->refcount++; |
| |
| // TODO invisible devices also? |
| if (composite) { |
| list_add_tail(&list_devices_pending, &dev->anode); |
| } else { |
| list_add_tail(&list_devices, &dev->anode); |
| } |
| |
| log(DEVLC, "devcoord: dev %p name='%s' ++ref=%d (child)\n", |
| parent, parent->name, parent->refcount); |
| |
| log(DEVLC, "devcoord: publish %p '%s' props=%u args='%s' parent=%p\n", |
| dev, dev->name, dev->prop_count, dev->args, dev->parent); |
| |
| if (composite) { |
| queue_work(&dev->work, WORK_COMPOSITE_DEVICE_ADDED, 0); |
| } else if (invisible) { |
| dev->flags |= DEV_CTX_INVISIBLE; |
| } else { |
| dc_notify(dev, DEVMGR_OP_DEVICE_ADDED); |
| queue_work(&dev->work, WORK_DEVICE_ADDED, 0); |
| } |
| return ZX_OK; |
| } |
| |
| static zx_status_t dc_make_visible(device_t* 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); |
| dc_notify(dev, DEVMGR_OP_DEVICE_ADDED); |
| queue_work(&dev->work, WORK_DEVICE_ADDED, 0); |
| } |
| 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 |
| static zx_status_t dc_remove_device(device_t* 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); |
| dc_release_device(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); |
| 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); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| log(DEVLC, "devcoord: remove %p name='%s' parent=%p\n", dev, dev->name, dev->parent); |
| dev->flags |= DEV_CTX_DEAD; |
| |
| // remove from devfs, preventing further OPEN attempts |
| devfs_unpublish(dev); |
| |
| // detach from devhost |
| devhost_t* dh = dev->host; |
| if (dh != NULL) { |
| dev->host = NULL; |
| list_delete(&dev->dhnode); |
| |
| // 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 |= DEV_HOST_DYING; |
| |
| device_t* next; |
| device_t* last = NULL; |
| while ((next = list_peek_head_type(&dh->devices, device_t, dhnode)) != NULL) { |
| if (last == next) { |
| // This shouldn't be possbile, but let's not infinite-loop if it happens |
| log(ERROR, "devcoord: fatal: failed to remove dev %p from devhost\n", next); |
| exit(1); |
| } |
| dc_remove_device(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. |
| } |
| |
| dc_release_devhost(dh); |
| } |
| |
| // if we have a parent, disconnect and downref it |
| device_t* parent = dev->parent; |
| if (parent != NULL) { |
| dev->parent = NULL; |
| if (dev->flags & DEV_CTX_PROXY) { |
| parent->proxy = NULL; |
| } else { |
| list_delete(&dev->node); |
| if (list_is_empty(&parent->children)) { |
| 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 == NULL) || !(parent->host->flags & DEV_HOST_DYING))) { |
| |
| log(DEVLC, "devcoord: bus device %p name='%s' is unbound\n", |
| parent, parent->name); |
| |
| //TODO: introduce timeout, exponential backoff |
| queue_work(&parent->work, WORK_DEVICE_ADDED, 0); |
| } |
| } |
| } |
| dc_release_device(parent); |
| } |
| |
| if (!(dev->flags & DEV_CTX_PROXY)) { |
| // remove from list of all devices |
| list_delete(&dev->anode); |
| dc_notify(dev, DEVMGR_OP_DEVICE_REMOVED); |
| } |
| |
| if (forced) { |
| // release the ref held by the devhost |
| dc_release_device(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; |
| } |
| |
| static zx_status_t dc_bind_device(device_t* dev, const char* drvlibname) { |
| log(INFO, "devcoord: dc_bind_device() '%s'\n", drvlibname); |
| |
| // 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[0] == 0); |
| |
| //TODO: disallow if we're in the middle of enumeration, etc |
| driver_t* drv; |
| list_for_every_entry(&list_drivers, drv, driver_t, node) { |
| if (autobind || !strcmp(drv->libname, drvlibname)) { |
| if (dc_drv_is_bindable(drv, dev->protocol_id, |
| dev->props, dev->prop_count, autobind)) { |
| log(SPEW, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv->name, dev->name); |
| dc_attempt_bind(drv, dev); |
| break; |
| } |
| } |
| } |
| |
| return ZX_OK; |
| }; |
| |
| static zx_status_t dc_handle_device_read(device_t* dev) { |
| dc_msg_t msg; |
| zx_handle_t hin[2]; |
| uint32_t msize = sizeof(msg); |
| uint32_t hcount = 2; |
| |
| 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 = zx_channel_read(dev->hrpc, 0, &msg, hin, |
| msize, hcount, &msize, &hcount)) < 0) { |
| return r; |
| } |
| |
| const void* data; |
| const char* name; |
| const char* args; |
| if ((r = dc_msg_unpack(&msg, msize, &data, &name, &args)) < 0) { |
| while (hcount > 0) { |
| zx_handle_close(hin[--hcount]); |
| } |
| return ZX_ERR_INTERNAL; |
| } |
| |
| dc_status_t dcs; |
| dcs.txid = msg.txid; |
| |
| switch (msg.op) { |
| case DC_OP_ADD_DEVICE: |
| case DC_OP_ADD_DEVICE_INVISIBLE: |
| if (hcount != 1) { |
| goto fail_wrong_hcount; |
| } |
| log(RPC_IN, "devcoord: rpc: add-device '%s' args='%s'\n", name, args); |
| if ((r = dc_add_device(dev, hin[0], &msg, name, args, data, |
| msg.op == DC_OP_ADD_DEVICE_INVISIBLE)) < 0) { |
| zx_handle_close(hin[0]); |
| } |
| break; |
| |
| case DC_OP_REMOVE_DEVICE: |
| if (hcount != 0) { |
| goto fail_wrong_hcount; |
| } |
| log(RPC_IN, "devcoord: rpc: remove-device '%s'\n", dev->name); |
| dc_remove_device(dev, false); |
| goto disconnect; |
| |
| case DC_OP_MAKE_VISIBLE: |
| if (hcount != 0) { |
| goto fail_wrong_hcount; |
| } |
| log(RPC_IN, "devcoord: rpc: make-visible '%s'\n", dev->name); |
| dc_make_visible(dev); |
| r = ZX_OK; |
| break; |
| |
| case DC_OP_BIND_DEVICE: |
| if (hcount != 0) { |
| goto fail_wrong_hcount; |
| } |
| log(RPC_IN, "devcoord: rpc: bind-device '%s'\n", dev->name); |
| r = dc_bind_device(dev, args); |
| break; |
| |
| case DC_OP_DM_COMMAND: |
| if (hcount > 1) { |
| goto fail_wrong_hcount; |
| } |
| if (hcount == 1) { |
| dmctl_socket = hin[0]; |
| } |
| r = handle_dmctl_write(msg.datalen, data); |
| if (dmctl_socket != ZX_HANDLE_INVALID) { |
| zx_handle_close(dmctl_socket); |
| dmctl_socket = ZX_HANDLE_INVALID; |
| } |
| break; |
| |
| case DC_OP_DM_OPEN_VIRTCON: |
| if (hcount != 1) { |
| goto fail_wrong_hcount; |
| } |
| if (zx_channel_write(virtcon_open, 0, NULL, 0, hin, 1) < 0) { |
| zx_handle_close(hin[0]); |
| } |
| r = ZX_OK; |
| break; |
| |
| case DC_OP_DM_WATCH: |
| if (hcount != 1) { |
| goto fail_wrong_hcount; |
| } |
| dc_watch(hin[0]); |
| r = ZX_OK; |
| break; |
| |
| case DC_OP_GET_TOPO_PATH: { |
| if (hcount != 0) { |
| goto fail_wrong_hcount; |
| } |
| struct { |
| dc_status_t rsp; |
| char path[DC_PATH_MAX]; |
| } reply; |
| if ((r = dc_get_topo_path(dev, reply.path, DC_PATH_MAX)) < 0) { |
| break; |
| } |
| reply.rsp.status = ZX_OK; |
| reply.rsp.txid = msg.txid; |
| if ((r = zx_channel_write(dev->hrpc, 0, &reply, sizeof(reply), NULL, 0)) < 0) { |
| return r; |
| } |
| return ZX_OK; |
| } |
| case DC_OP_STATUS: { |
| if (hcount != 0) { |
| goto fail_wrong_hcount; |
| } |
| // all of these return directly and do not write a |
| // reply, since this message is a reply itself |
| pending_t* pending = list_remove_tail_type(&dev->pending, pending_t, node); |
| if (pending == NULL) { |
| log(ERROR, "devcoord: rpc: spurious status message\n"); |
| return ZX_OK; |
| } |
| switch (pending->op) { |
| case PENDING_BIND: |
| if (msg.status != ZX_OK) { |
| log(ERROR, "devcoord: rpc: bind-driver '%s' status %d\n", |
| dev->name, msg.status); |
| } else { |
| dc_notify(dev, DEVMGR_OP_DEVICE_CHANGED); |
| } |
| //TODO: try next driver, clear BOUND flag |
| break; |
| } |
| free(pending); |
| return ZX_OK; |
| } |
| |
| default: |
| log(ERROR, "devcoord: invalid rpc op %08x\n", msg.op); |
| r = ZX_ERR_NOT_SUPPORTED; |
| goto fail_close_handles; |
| } |
| |
| done: |
| dcs.status = r; |
| if ((r = zx_channel_write(dev->hrpc, 0, &dcs, sizeof(dcs), NULL, 0)) < 0) { |
| return r; |
| } |
| return ZX_OK; |
| |
| disconnect: |
| dcs.status = ZX_OK; |
| zx_channel_write(dev->hrpc, 0, &dcs, sizeof(dcs), NULL, 0); |
| return ZX_ERR_STOP; |
| |
| fail_wrong_hcount: |
| r = ZX_ERR_INVALID_ARGS; |
| fail_close_handles: |
| while (hcount > 0) { |
| zx_handle_close(hin[--hcount]); |
| } |
| goto done; |
| } |
| |
| #define dev_from_ph(ph) containerof(ph, device_t, ph) |
| |
| // handle inbound RPCs from devhost to devices |
| static zx_status_t dc_handle_device(port_handler_t* ph, zx_signals_t signals, uint32_t evt) { |
| device_t* dev = dev_from_ph(ph); |
| |
| if (signals & ZX_CHANNEL_READABLE) { |
| zx_status_t r; |
| if ((r = dc_handle_device_read(dev)) < 0) { |
| if (r != ZX_ERR_STOP) { |
| log(ERROR, "devcoord: device %p name='%s' rpc status: %d\n", |
| dev, dev->name, r); |
| } |
| dc_remove_device(dev, true); |
| return ZX_ERR_STOP; |
| } |
| return ZX_OK; |
| } |
| if (signals & ZX_CHANNEL_PEER_CLOSED) { |
| log(ERROR, "devcoord: device %p name='%s' disconnected!\n", dev, dev->name); |
| dc_remove_device(dev, true); |
| return ZX_ERR_STOP; |
| } |
| log(ERROR, "devcoord: no work? %08x\n", signals); |
| return ZX_OK; |
| } |
| |
| // send message to devhost, requesting the creation of a device |
| static zx_status_t dh_create_device(device_t* dev, devhost_t* dh, |
| const char* args, zx_handle_t rpc_proxy) { |
| dc_msg_t msg; |
| uint32_t mlen; |
| zx_status_t r; |
| |
| if ((r = dc_msg_pack(&msg, &mlen, NULL, 0, dev->libname, args)) < 0) { |
| return r; |
| } |
| |
| uint32_t hcount = 0; |
| zx_handle_t handle[3], hrpc; |
| if ((r = zx_channel_create(0, handle, &hrpc)) < 0) { |
| return r; |
| } |
| hcount++; |
| |
| if (dev->libname[0]) { |
| if ((r = libname_to_vmo(dev->libname, handle + 1)) < 0) { |
| goto fail; |
| } |
| hcount++; |
| msg.op = DC_OP_CREATE_DEVICE; |
| } else { |
| msg.op = DC_OP_CREATE_DEVICE_STUB; |
| } |
| |
| if (rpc_proxy) { |
| handle[hcount++] = rpc_proxy; |
| } |
| |
| msg.txid = 0; |
| msg.protocol_id = dev->protocol_id; |
| |
| if ((r = zx_channel_write(dh->hrpc, 0, &msg, mlen, handle, hcount)) < 0) { |
| goto fail; |
| } |
| |
| dev->hrpc = hrpc; |
| dev->ph.handle = hrpc; |
| dev->ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED; |
| dev->ph.func = dc_handle_device; |
| if ((r = port_wait(&dc_port, &dev->ph)) < 0) { |
| goto fail_watch; |
| } |
| dev->host = dh; |
| dh->refcount++; |
| list_add_tail(&dh->devices, &dev->dhnode); |
| return ZX_OK; |
| |
| fail: |
| while (hcount > 0) { |
| zx_handle_close(handle[--hcount]); |
| } |
| fail_watch: |
| zx_handle_close(hrpc); |
| return r; |
| } |
| |
| static zx_status_t dc_create_proxy(device_t* parent) { |
| if (parent->proxy != NULL) { |
| return ZX_OK; |
| } |
| |
| size_t namelen = strlen(parent->name); |
| size_t liblen = strlen(parent->libname); |
| size_t devlen = sizeof(device_t) + namelen + liblen + 2; |
| |
| // non-immortal devices, use foo.proxy.so for |
| // their proxy devices instead of foo.so |
| bool proxylib = !(parent->flags & DEV_CTX_IMMORTAL); |
| |
| if (proxylib) { |
| if (liblen < 3) { |
| return ZX_ERR_INTERNAL; |
| } |
| // space for ".proxy" |
| devlen += 6; |
| } |
| |
| device_t* dev = calloc(1, devlen); |
| if (dev == NULL) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| char* text = (char*) (dev + 1); |
| memcpy(text, parent->name, namelen + 1); |
| dev->name = text; |
| text += namelen + 1; |
| memcpy(text, parent->libname, liblen + 1); |
| if (proxylib) { |
| memcpy(text + liblen - 3, ".proxy.so", 10); |
| } |
| dev->libname = text; |
| |
| list_initialize(&dev->children); |
| list_initialize(&dev->pending); |
| dev->flags = DEV_CTX_PROXY; |
| dev->protocol_id = parent->protocol_id; |
| dev->parent = parent; |
| dev->refcount = 1; |
| parent->proxy = dev; |
| parent->refcount++; |
| log(DEVLC, "devcoord: dev %p name='%s' ++ref=%d (proxy)\n", |
| parent, parent->name, parent->refcount); |
| return ZX_OK; |
| } |
| |
| // send message to devhost, requesting the binding of a driver to a device |
| static zx_status_t dh_bind_driver(device_t* dev, const char* libname) { |
| dc_msg_t msg; |
| uint32_t mlen; |
| |
| pending_t* pending = malloc(sizeof(pending_t)); |
| if (pending == NULL) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t r; |
| if ((r = dc_msg_pack(&msg, &mlen, NULL, 0, libname, NULL)) < 0) { |
| free(pending); |
| return r; |
| } |
| |
| zx_handle_t vmo; |
| if ((r = libname_to_vmo(libname, &vmo)) < 0) { |
| free(pending); |
| return r; |
| } |
| |
| msg.txid = 0; |
| msg.op = DC_OP_BIND_DRIVER; |
| |
| if ((r = zx_channel_write(dev->hrpc, 0, &msg, mlen, &vmo, 1)) < 0) { |
| free(pending); |
| return r; |
| } |
| |
| dev->flags |= DEV_CTX_BOUND; |
| pending->op = PENDING_BIND; |
| pending->ctx = NULL; |
| list_add_tail(&dev->pending, &pending->node); |
| return ZX_OK; |
| } |
| |
| static zx_status_t dh_connect_proxy(device_t* dev, zx_handle_t h) { |
| dc_msg_t msg; |
| uint32_t mlen; |
| zx_status_t r; |
| if ((r = dc_msg_pack(&msg, &mlen, NULL, 0, NULL, NULL)) < 0) { |
| zx_handle_close(h); |
| return r; |
| } |
| msg.txid = 0; |
| msg.op = DC_OP_CONNECT_PROXY; |
| if ((r = zx_channel_write(dev->hrpc, 0, &msg, mlen, &h, 1)) < 0) { |
| zx_handle_close(h); |
| } |
| return r; |
| } |
| |
| static zx_status_t dh_suspend(device_t* dev, uint32_t flags) { |
| dc_msg_t msg; |
| uint32_t mlen; |
| zx_status_t r; |
| if ((r = dc_msg_pack(&msg, &mlen, NULL, 0, NULL, NULL)) < 0) { |
| return r; |
| } |
| msg.txid = 0; |
| msg.op = DC_OP_SUSPEND; |
| msg.value = flags; |
| zx_handle_t rpc = dev->proxy ? dev->proxy->hrpc : dev->hrpc; |
| if ((r = zx_channel_write(rpc, 0, &msg, mlen, NULL, 0)) != ZX_OK) { |
| return r; |
| } |
| |
| // Wait 5 seconds for the other side to finish up. Use zx_object_wait_one |
| // rather than just a nanosleep, so that on ARM this doesn't hang for 5s. |
| // TODO(swetland/teisenbe): This should use a completion mechanism to get |
| // the response from the other side rather than just timing out. |
| return zx_object_wait_one(rpc, ZX_CHANNEL_PEER_CLOSED, zx_deadline_after(ZX_SEC(5)), NULL); |
| } |
| |
| static zx_status_t dc_prepare_proxy(device_t* dev) { |
| if (dev->flags & DEV_CTX_PROXY) { |
| log(ERROR, "devcoord: cannot proxy a proxy: %s\n", dev->name); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // proxy args are "processname,args" |
| const char* arg0 = dev->args; |
| const char* arg1 = strchr(arg0, ','); |
| if (arg1 == NULL) { |
| 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(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 == NULL) { |
| zx_handle_t h0 = ZX_HANDLE_INVALID, h1 = ZX_HANDLE_INVALID; |
| |
| // 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 |
| if ((r = zx_channel_create(0, &h0, &h1)) < 0) { |
| log(ERROR, "devcoord: cannot create proxy rpc channel: %d\n", r); |
| return r; |
| } |
| } |
| if ((r = dc_new_devhost(devhostname, &dev->proxy->host)) < 0) { |
| log(ERROR, "devcoord: dh_new_devhost: %d\n", r); |
| zx_handle_close(h0); |
| zx_handle_close(h1); |
| return r; |
| } |
| if ((r = dh_create_device(dev->proxy, dev->proxy->host, arg1, h1)) < 0) { |
| log(ERROR, "devcoord: dh_create_device: %d\n", r); |
| zx_handle_close(h0); |
| return r; |
| } |
| if (need_proxy_rpc) { |
| if ((r = dh_connect_proxy(dev, h0)) < 0) { |
| log(ERROR, "devcoord: dh_connect_proxy: %d\n", r); |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t dc_attempt_bind(driver_t* drv, device_t* 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 == NULL) { |
| log(ERROR, "devcoord: can't bind to device without devhost\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| return dh_bind_driver(dev, drv->libname); |
| } |
| |
| zx_status_t r; |
| if ((r = dc_prepare_proxy(dev)) < 0) { |
| return r; |
| } |
| |
| return dh_bind_driver(dev->proxy, drv->libname); |
| } |
| |
| static void dc_handle_new_device(device_t* dev) { |
| driver_t* drv; |
| |
| list_for_every_entry(&list_drivers, drv, driver_t, node) { |
| if (dc_drv_is_bindable(drv, dev->protocol_id, |
| dev->props, dev->prop_count, true)) { |
| log(SPEW, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv->name, dev->name); |
| |
| dc_attempt_bind(drv, dev); |
| if (!(dev->flags & DEV_CTX_MULTI_BIND)) { |
| break; |
| } |
| } |
| } |
| } |
| |
| static void dc_handle_new_composite_device(device_t* dev) { |
| bool satisfied; |
| for (uint32_t i = 0; i < dev->dep_count; i++) { |
| device_t* other; |
| satisfied = false; |
| list_for_every_entry(&list_devices, other, device_t, anode) { |
| if (other->protocol_id == ZX_PROTOCOL_I2C_BUS) { |
| //printf("devcoord: dev='%s' satisfies dependency %u for dev='%s'?\n", |
| // other->name, i, dev->name); |
| } |
| if (dc_is_bindable(dev->deps + i, other->protocol_id, |
| other->props, other->prop_count)) { |
| satisfied = true; |
| } |
| } |
| if (!satisfied) { |
| break; |
| } |
| } |
| printf("devcoord: dependencies for dev='%s' satisfied %d\n", dev->name, satisfied); |
| if (satisfied) { |
| list_delete(&dev->anode); |
| list_add_tail(&list_devices, &dev->anode); |
| devfs_advertise(dev); |
| dc_notify(dev, DEVMGR_OP_DEVICE_ADDED); |
| dc_handle_new_device(dev); |
| } |
| } |
| |
| // 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_t* drv) { |
| return (drv->binding_size == sizeof(misc_device_binding)) && |
| (memcmp(&misc_device_binding, drv->binding, sizeof(misc_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_t* drv) { |
| return (drv->binding_size == sizeof(root_device_binding)) && |
| (memcmp(&root_device_binding, drv->binding, sizeof(root_device_binding)) == 0); |
| } |
| |
| static work_t new_driver_work; |
| |
| // dc_driver_added is called from driver enumeration either before |
| // or after the devcoordinator starts running. If after, it's added |
| // to the list of new drivers and work is queued to process it. If |
| // before it's added to the list of all drivers or fallback list. |
| void dc_driver_added(driver_t* drv, const char* version) { |
| if (dc_running) { |
| list_add_head(&list_drivers_new, &drv->node); |
| if (new_driver_work.op == WORK_IDLE) { |
| queue_work(&new_driver_work, WORK_DRIVER_ADDED, 0); |
| } |
| return; |
| } |
| //TODO: real priority scheme |
| if (version[0] == '*') { |
| // fallback driver, load only if all else fails |
| list_add_tail(&list_drivers_fallback, &drv->node); |
| } else if (version[0] == '!') { |
| // debugging / development hack |
| // prioritize drivers with version "!..." over others |
| list_add_head(&list_drivers, &drv->node); |
| } else { |
| list_add_tail(&list_drivers, &drv->node); |
| } |
| } |
| |
| device_t* coordinator_init(zx_handle_t root_job) { |
| printf("coordinator_init()\n"); |
| |
| zx_status_t status = zx_job_create(root_job, 0u, &devhost_job); |
| if (status < 0) { |
| log(ERROR, "devcoord: unable to create devhost job\n"); |
| } |
| static const zx_policy_basic_t policy[] = { |
| { ZX_POL_BAD_HANDLE, ZX_POL_ACTION_EXCEPTION }, |
| }; |
| status = zx_job_set_policy(devhost_job, ZX_JOB_POL_RELATIVE, |
| ZX_JOB_POL_BASIC, &policy, countof(policy)); |
| if (status < 0) { |
| log(ERROR, "devcoord: zx_job_set_policy() failed\n"); |
| } |
| zx_object_set_property(devhost_job, ZX_PROP_NAME, "zircon-drivers", 15); |
| |
| port_init(&dc_port); |
| |
| return &root_device; |
| } |
| |
| // dc_bind_driver is called when a new driver becomes available to |
| // the devcoordinator. Existing devices are inspected to see if the |
| // new driver is bindable to them (unless they are already bound). |
| void dc_bind_driver(driver_t* drv) { |
| if (dc_running) { |
| printf("devcoord: driver '%s' added\n", drv->name); |
| } |
| if (is_root_driver(drv)) { |
| dc_attempt_bind(drv, &root_device); |
| } else if (is_misc_driver(drv)) { |
| dc_attempt_bind(drv, &misc_device); |
| } else if (dc_running) { |
| device_t* dev; |
| list_for_every_entry(&list_devices, dev, device_t, anode) { |
| if (dev->flags & (DEV_CTX_BOUND | DEV_CTX_DEAD | DEV_CTX_ZOMBIE)) { |
| // if device is already bound or being destroyed, skip it |
| continue; |
| } |
| if (dc_drv_is_bindable(drv, dev->protocol_id, |
| dev->props, dev->prop_count, true)) { |
| log(INFO, "devcoord: drv='%s' bindable to dev='%s'\n", |
| drv->name, dev->name); |
| |
| dc_attempt_bind(drv, dev); |
| } |
| } |
| } |
| } |
| |
| void dc_handle_new_driver(void) { |
| driver_t* drv; |
| while ((drv = list_remove_head_type(&list_drivers_new, driver_t, node)) != NULL) { |
| list_add_tail(&list_drivers, &drv->node); |
| dc_bind_driver(drv); |
| } |
| } |
| |
| #define CTL_SCAN_SYSTEM 1 |
| |
| static bool system_available; |
| static bool system_loaded; |
| |
| static zx_status_t dc_control_event(port_handler_t* ph, zx_signals_t signals, uint32_t evt) { |
| switch (evt) { |
| case CTL_SCAN_SYSTEM: |
| if (!system_loaded) { |
| system_loaded = true; |
| find_loadable_drivers("/system/driver"); |
| find_loadable_drivers("/system/lib/driver"); |
| } |
| break; |
| } |
| return ZX_OK; |
| } |
| |
| static port_handler_t control_handler = { |
| .func = dc_control_event, |
| }; |
| |
| |
| void load_system_drivers(void) { |
| system_available = true; |
| port_queue(&dc_port, &control_handler, CTL_SCAN_SYSTEM); |
| } |
| |
| void coordinator(void) { |
| log(INFO, "devmgr: coordinator()\n"); |
| |
| if (getenv_bool("devmgr.verbose", false)) { |
| log_flags |= LOG_DEVLC; |
| } |
| |
| devfs_publish(&root_device, &misc_device); |
| devfs_publish(&root_device, &sys_device); |
| |
| find_loadable_drivers("/boot/driver"); |
| find_loadable_drivers("/boot/driver/test"); |
| find_loadable_drivers("/boot/lib/driver"); |
| |
| // 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 (system_available) { |
| dc_control_event(&control_handler, 0, CTL_SCAN_SYSTEM); |
| } |
| |
| // x86 platforms use acpi as the system device |
| // all other platforms use the platform bus |
| #if defined(__x86_64__) |
| sys_device.libname = "/boot/driver/bus-acpi.so"; |
| #else |
| sys_device.libname = "/boot/driver/platform-bus.so"; |
| #endif |
| dc_prepare_proxy(&sys_device); |
| |
| if (require_system) { |
| printf("devcoord: full system required, ignoring fallback drivers\n"); |
| } else { |
| driver_t* drv; |
| while ((drv = list_remove_tail_type(&list_drivers_fallback, driver_t, node)) != NULL) { |
| list_add_tail(&list_drivers, &drv->node); |
| } |
| } |
| |
| // Initial bind attempt for drivers enumerated at startup. |
| driver_t* drv; |
| list_for_every_entry(&list_drivers, drv, driver_t, node) { |
| dc_bind_driver(drv); |
| } |
| |
| dc_running = true; |
| |
| for (;;) { |
| zx_status_t status; |
| if (list_is_empty(&list_pending_work)) { |
| status = port_dispatch(&dc_port, ZX_TIME_INFINITE, true); |
| } else { |
| status = port_dispatch(&dc_port, 0, true); |
| if (status == ZX_ERR_TIMED_OUT) { |
| process_work(list_remove_head_type(&list_pending_work, work_t, node)); |
| continue; |
| } |
| } |
| if (status != ZX_OK) { |
| log(ERROR, "devcoord: port dispatch ended: %d\n", status); |
| } |
| } |
| } |