blob: 6fb67111fdfd8be513eed74d27f965d38432ee3b [file] [log] [blame]
// Copyright 2016 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 "devcoordinator.h"
#include "devmgr.h"
#include "memfs-private.h"
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <zircon/device/vfs.h>
#include <fdio/remoteio.h>
#include <fdio/util.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct dc_watcher watcher_t;
typedef struct dc_iostate iostate_t;
struct dc_watcher {
watcher_t* next;
devnode_t* devnode;
uint32_t mask;
zx_handle_t handle;
};
struct dc_devnode {
const char* name;
uint64_t ino;
// NULL if we are a pure directory node,
// otherwise the device we are referencing
device_t* device;
watcher_t* watchers;
// entry in our parent devnode's children list
list_node_t node;
// list of our child devnodes
list_node_t children;
// list of attached iostates
list_node_t iostate;
// used to assign unique small device numbers
// for class device links
uint32_t seqcount;
};
struct dc_iostate {
port_handler_t ph;
// entry in our devnode's iostate list
list_node_t node;
// pointer to our devnode, NULL if it has been removed
devnode_t* devnode;
uint64_t readdir_ino;
};
extern port_t dc_port;
static uint64_t next_ino = 2;
static devnode_t root_devnode = {
.name = "",
.ino = 1,
.children = LIST_INITIAL_VALUE(root_devnode.children),
.iostate = LIST_INITIAL_VALUE(root_devnode.iostate),
};
static devnode_t* class_devnode;
static zx_status_t dc_rio_handler(port_handler_t* ph, zx_signals_t signals, uint32_t evt);
static devnode_t* devfs_mkdir(devnode_t* parent, const char* name);
#define PNMAX 16
static const char* proto_name(uint32_t id, char buf[PNMAX]) {
switch (id) {
#define DDK_PROTOCOL_DEF(tag, val, name, flags) case val: return name;
#include <ddk/protodefs.h>
default:
snprintf(buf, PNMAX, "proto-%08x", id);
return buf;
}
}
typedef struct {
const char* name;
devnode_t* devnode;
uint32_t id;
uint32_t flags;
} pinfo_t;
static pinfo_t proto_info[] = {
#define DDK_PROTOCOL_DEF(tag, val, name, flags) { name, NULL, val, flags },
#include <ddk/protodefs.h>
{ NULL, NULL, 0, 0 },
};
static devnode_t* proto_dir(uint32_t id) {
for (pinfo_t* info = proto_info; info->name; info++) {
if (info->id == id) {
return info->devnode;
}
}
return NULL;
}
static void prepopulate_protocol_dirs(void) {
class_devnode = devfs_mkdir(&root_devnode, "class");
for (pinfo_t* info = proto_info; info->name; info++) {
if (!(info->flags & PF_NOPUB)) {
info->devnode = devfs_mkdir(class_devnode, info->name);
}
}
}
static zx_status_t iostate_create(devnode_t* dn, zx_handle_t h) {
iostate_t* ios = calloc(1, sizeof(iostate_t));
if (ios == NULL) {
return ZX_ERR_NO_MEMORY;
}
ios->ph.handle = h;
ios->ph.waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
ios->ph.func = dc_rio_handler;
ios->devnode = dn;
list_add_tail(&dn->iostate, &ios->node);
zx_status_t r;
if ((r = port_wait(&dc_port, &ios->ph)) < 0) {
list_delete(&ios->node);
free(ios);
}
return r;
}
static void iostate_destroy(iostate_t* ios) {
if (ios->devnode) {
list_delete(&ios->node);
ios->devnode = NULL;
}
zx_handle_close(ios->ph.handle);
ios->ph.handle = ZX_HANDLE_INVALID;
free(ios);
}
// A devnode is a directory (from stat's perspective) if
// it has children, or if it doesn't have a device, or if
// its device has no rpc handle
static bool devnode_is_dir(devnode_t* dn) {
if (list_is_empty(&dn->children)) {
return (dn->device == NULL) || (dn->device->hrpc == ZX_HANDLE_INVALID);
}
return true;
}
// Local devnodes are ones that we should not hand off OPEN
// RPCs to the underlying devhost
static bool devnode_is_local(devnode_t* dn) {
if (dn->device == NULL) {
return true;
}
if (dn->device->hrpc == ZX_HANDLE_INVALID) {
return true;
}
if (dn->device->flags & DEV_CTX_MUST_ISOLATE) {
return true;
}
return false;
}
static void devfs_notify(devnode_t* dn, const char* name, unsigned op) {
watcher_t* w = dn->watchers;
if (w == NULL) {
return;
}
size_t len = strlen(name);
if (len > VFS_WATCH_NAME_MAX) {
return;
}
uint8_t msg[VFS_WATCH_NAME_MAX + 2];
msg[0] = op;
msg[1] = len;
memcpy(msg + 2, name, len);
// convert to mask
op = (1u << op);
watcher_t** wp;
watcher_t* next;
for (wp = &dn->watchers; w != NULL; w = next) {
next = w->next;
if (!(w->mask & op)) {
continue;
}
if (zx_channel_write(w->handle, 0, msg, len + 2, NULL, 0) < 0) {
*wp = next;
zx_handle_close(w->handle);
free(w);
} else {
wp = &w->next;
}
}
}
static zx_status_t devfs_watch(devnode_t* dn, zx_handle_t h, uint32_t mask) {
watcher_t* watcher = calloc(1, sizeof(watcher_t));
if (watcher == NULL) {
return ZX_ERR_NO_MEMORY;
}
watcher->devnode = dn;
watcher->next = dn->watchers;
watcher->handle = h;
watcher->mask = mask;
dn->watchers = watcher;
if (mask & VFS_WATCH_MASK_EXISTING) {
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (child->device && (child->device->flags & DEV_CTX_INVISIBLE)) {
continue;
}
//TODO: send multiple per write
devfs_notify(dn, child->name, VFS_WATCH_EVT_EXISTING);
}
devfs_notify(dn, "", VFS_WATCH_EVT_IDLE);
}
// Don't send EXISTING or IDLE events from now on...
watcher->mask &= ~(VFS_WATCH_MASK_EXISTING | VFS_WATCH_MASK_IDLE);
return ZX_OK;
}
// If namelen is nonzero, it is the null-terminator-inclusive length
// of name, which should be copied into the devnode. Otherwise name
// is guaranteed to exist for the lifetime of the devnode.
static devnode_t* devfs_mknode(device_t* dev, const char* name, size_t namelen) {
devnode_t* dn = calloc(1, sizeof(devnode_t) + namelen);
if (dn == NULL) {
return NULL;
}
if (namelen > 0) {
char* p = (char*) (dn + 1);
memcpy(p, name, namelen);
dn->name = p;
} else {
dn->name = name;
}
dn->ino = next_ino++;
dn->device = dev;
list_initialize(&dn->children);
list_initialize(&dn->iostate);
return dn;
}
static devnode_t* devfs_mkdir(devnode_t* parent, const char* name) {
devnode_t* dn = devfs_mknode(NULL, name, 0);
if (dn == NULL) {
return NULL;
}
list_add_tail(&parent->children, &dn->node);
return dn;
}
static devnode_t* devfs_lookup(devnode_t* parent, const char* name) {
devnode_t* child;
list_for_every_entry(&parent->children, child, devnode_t, node) {
if (!strcmp(name, child->name)) {
return child;
}
}
return NULL;
}
void devfs_advertise(device_t* dev) {
if (dev->link) {
devnode_t* dir = proto_dir(dev->protocol_id);
devfs_notify(dir, dev->link->name, VFS_WATCH_EVT_ADDED);
}
devfs_notify(dev->parent->self, dev->self->name, VFS_WATCH_EVT_ADDED);
}
zx_status_t devfs_publish(device_t* parent, device_t* dev) {
if ((parent->self == NULL) || (dev->self != NULL) || (dev->link != NULL)) {
return ZX_ERR_INTERNAL;
}
devnode_t* dnself = devfs_mknode(dev, dev->name, 0);
if (dnself == NULL) {
return ZX_ERR_NO_MEMORY;
}
if ((dev->protocol_id == ZX_PROTOCOL_MISC_PARENT) ||
(dev->protocol_id == ZX_PROTOCOL_MISC)) {
// misc devices are singletons, not a class
// in the sense of other device classes.
// They do not get aliases in /dev/class/misc/...
// instead they exist only under their parent
// device.
goto done;
}
// Create link in /dev/class/... if this id has a published class
devnode_t* dir = proto_dir(dev->protocol_id);
if (dir != NULL) {
char tmp[32];
const char* name = dev->name;
size_t namelen = 0;
if ((dev->protocol_id != ZX_PROTOCOL_MISC) &&
(dev->protocol_id != ZX_PROTOCOL_CONSOLE)) {
for (unsigned n = 0; n < 1000; n++) {
snprintf(tmp, sizeof(tmp), "%03u", (dir->seqcount++) % 1000);
if (devfs_lookup(dir, tmp) == NULL) {
name = tmp;
namelen = 4;
goto got_name;
}
}
free(dnself);
return ZX_ERR_ALREADY_EXISTS;
got_name:
;
}
devnode_t* dnlink = devfs_mknode(dev, name, namelen);
if (dnlink == NULL) {
free(dnself);
return ZX_ERR_NO_MEMORY;
}
// add link node to class directory
list_add_tail(&dir->children, &dnlink->node);
dev->link = dnlink;
}
done:
// add self node to parent directory
list_add_tail(&parent->self->children, &dnself->node);
dev->self = dnself;
if (!(dev->flags & DEV_CTX_INVISIBLE)) {
devfs_advertise(dev);
}
return ZX_OK;
}
static void _devfs_remove(devnode_t* dn) {
if (list_in_list(&dn->node)) {
list_delete(&dn->node);
}
// disconnect from device
if (dn->device != NULL) {
if (dn->device->self == dn) {
dn->device->self = NULL;
}
if (dn->device->link == dn) {
dn->device->link = NULL;
}
dn->device = NULL;
}
// detach all connected iostates
iostate_t* ios;
list_for_every_entry(&dn->iostate, ios, iostate_t, node) {
ios->devnode = NULL;
zx_handle_close(ios->ph.handle);
ios->ph.handle = ZX_HANDLE_INVALID;
}
if ((dn->device == NULL) ||
!(dn->device->flags & DEV_CTX_INVISIBLE)) {
devfs_notify(dn, "", VFS_WATCH_EVT_DELETED);
}
// destroy all watchers
watcher_t* watcher;
watcher_t* next;
for (watcher = dn->watchers; watcher != NULL; watcher = next) {
next = watcher->next;
zx_handle_close(watcher->handle);
free(watcher);
}
dn->watchers = NULL;
// detach children
while (list_remove_head(&dn->children) != NULL) {
// they will be unpublished when the devices they're
// associated with are eventually destroyed
}
}
void devfs_unpublish(device_t* dev) {
if (dev->self != NULL) {
_devfs_remove(dev->self);
dev->self = NULL;
}
if (dev->link != NULL) {
_devfs_remove(dev->link);
dev->link = NULL;
}
}
static zx_status_t devfs_walk(devnode_t** _dn, char* path, char** pathout) {
devnode_t* dn = *_dn;
again:
if ((path == NULL) || (path[0] == 0)) {
*_dn = dn;
return ZX_OK;
}
char* name = path;
char* undo = NULL;
if ((path = strchr(path, '/')) != NULL) {
undo = path;
*path++ = 0;
}
if (name[0] == 0) {
return ZX_ERR_BAD_PATH;
}
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (!strcmp(child->name, name)) {
if(child->device && (child->device->flags & DEV_CTX_INVISIBLE)) {
continue;
}
dn = child;
goto again;
}
}
if (dn == *_dn) {
return ZX_ERR_NOT_FOUND;
}
if (undo) {
*undo = '/';
}
*_dn = dn;
*pathout = name;
return ZX_ERR_NEXT;
}
static void devfs_open(devnode_t* dirdn, zx_handle_t h, char* path, uint32_t flags) {
if (!strcmp(path, ".")) {
path = NULL;
}
devnode_t* dn = dirdn;
zx_status_t r = devfs_walk(&dn, path, &path);
bool pipeline = flags & O_PIPELINE;
if (r == ZX_ERR_NEXT) {
// we only partially matched -- there's more path to walk
if ((dn->device == NULL) || (dn->device->hrpc == ZX_HANDLE_INVALID)) {
// no remote to pass this on to
r = ZX_ERR_NOT_FOUND;
} else if (flags & (O_NOREMOTE | O_DIRECTORY)) {
// local requested, but this is remote only
r = ZX_ERR_NOT_SUPPORTED;
} else {
r = ZX_OK;
}
} else {
path = (char*) ".";
}
if (r < 0) {
fail:
if (!pipeline) {
zxrio_object_t obj;
obj.status = r;
obj.type = 0;
zx_channel_write(h, 0, &obj, ZXRIO_OBJECT_MINSIZE, NULL, 0);
}
zx_handle_close(h);
return;
}
// If we are a local-only node, or we are asked to not go remote,
// or we are asked to open-as-a-directory, open locally:
if ((flags & (O_NOREMOTE | O_DIRECTORY)) || devnode_is_local(dn)) {
if ((r = iostate_create(dn, h)) < 0) {
goto fail;
}
if (!pipeline) {
zxrio_object_t obj;
obj.status = ZX_OK;
obj.type = FDIO_PROTOCOL_REMOTE;
zx_channel_write(h, 0, &obj, ZXRIO_OBJECT_MINSIZE, NULL, 0);
}
return;
}
// Otherwise we will pass the request on to the remote
zxrio_msg_t msg;
memset(&msg, 0, ZXRIO_HDR_SZ);
msg.op = ZXRIO_OPEN;
msg.datalen = strlen(path);
msg.arg = flags;
msg.hcount = 1;
msg.handle[0] = h;
memcpy(msg.data, path, msg.datalen);
if ((r = zx_channel_write(dn->device->hrpc, 0, &msg, ZXRIO_HDR_SZ + msg.datalen,
msg.handle, 1)) < 0) {
goto fail;
}
}
static zx_status_t fill_dirent(vdirent_t* de, size_t delen,
const char* name, size_t len, uint32_t type) {
size_t sz = sizeof(vdirent_t) + len + 1;
// round up to uint32 aligned
if (sz & 3)
sz = (sz + 3) & (~3);
if (sz > delen)
return ZX_ERR_INVALID_ARGS;
de->size = sz;
de->type = type;
memcpy(de->name, name, len);
de->name[len] = 0;
return sz;
}
static zx_status_t devfs_readdir(devnode_t* dn, uint64_t* _ino, void* data, size_t len) {
void* ptr = data;
uint64_t ino = *_ino;
devnode_t* child;
list_for_every_entry(&dn->children, child, devnode_t, node) {
if (child->ino <= ino) {
continue;
}
if (child->device == NULL) {
// "pure" directories (like /dev/class/$NAME) do not show up
// if they have no children, to avoid clutter and confusion.
// They remain openable, so they can be watched.
if (list_is_empty(&child->children)) {
continue;
}
} else {
// invisible devices also do not show up
if (child->device->flags & DEV_CTX_INVISIBLE) {
continue;
}
}
ino = child->ino;
zx_status_t r = fill_dirent(ptr, len, child->name, strlen(child->name),
VTYPE_TO_DTYPE(V_TYPE_DIR));
if (r < 0) {
break;
}
ptr += r;
len -= r;
}
*_ino = ino;
return ptr - data;
}
static zx_status_t devfs_rio_handler(zxrio_msg_t* msg, void* cookie) {
iostate_t* ios = cookie;
devnode_t* dn = ios->devnode;
if (dn == NULL) {
return ZX_ERR_PEER_CLOSED;
}
// ensure handle count specified by opcode matches reality
if (msg->hcount != ZXRIO_HC(msg->op)) {
return ZX_ERR_IO;
}
msg->hcount = 0;
uint32_t len = msg->datalen;
int32_t arg = msg->arg;
msg->datalen = 0;
switch (ZXRIO_OP(msg->op)) {
case ZXRIO_CLONE:
msg->data[0] = 0;
devfs_open(dn, msg->handle[0], (char*) msg->data, arg | O_NOREMOTE);
return ERR_DISPATCHER_INDIRECT;
case ZXRIO_OPEN:
if ((len < 1) || (len > 1024)) {
zx_handle_close(msg->handle[0]);
} else {
msg->data[len] = 0;
devfs_open(dn, msg->handle[0], (char*) msg->data, arg);
}
return ERR_DISPATCHER_INDIRECT;
case ZXRIO_STAT:
msg->datalen = sizeof(vnattr_t);
vnattr_t* attr = (void*)msg->data;
memset(attr, 0, sizeof(vnattr_t));
if (devnode_is_dir(dn)) {
attr->mode = V_TYPE_DIR | V_IRUSR | V_IWUSR;
} else {
attr->mode = V_TYPE_CDEV | V_IRUSR | V_IWUSR;
}
attr->size = 0;
attr->nlink = 1;
return msg->datalen;
case ZXRIO_READDIR:
if (arg > FDIO_CHUNK_SIZE) {
return ZX_ERR_INVALID_ARGS;
}
if (msg->arg2.off == READDIR_CMD_RESET) {
ios->readdir_ino = 0;
}
zx_status_t r = devfs_readdir(dn, &ios->readdir_ino, msg->data, arg);
if (r >= 0) {
msg->datalen = r;
}
return r;
case ZXRIO_IOCTL_1H:
switch (msg->arg2.op) {
case IOCTL_VFS_WATCH_DIR: {
vfs_watch_dir_t* wd = (vfs_watch_dir_t*) msg->data;
if ((len != sizeof(vfs_watch_dir_t)) ||
(wd->options != 0) ||
(wd->mask & (~VFS_WATCH_MASK_ALL))) {
r = ZX_ERR_INVALID_ARGS;
} else {
r = devfs_watch(dn, msg->handle[0], wd->mask);
}
if (r != ZX_OK) {
zx_handle_close(msg->handle[0]);
}
return r;
}
}
break;
case ZXRIO_IOCTL:
switch (msg->arg2.op) {
case IOCTL_VFS_QUERY_FS: {
const char* devfs_name = "devfs";
if (arg < (int32_t) (sizeof(vfs_query_info_t) + strlen(devfs_name))) {
return ZX_ERR_INVALID_ARGS;
}
vfs_query_info_t* info = (vfs_query_info_t*) msg->data;
memset(info, 0, sizeof(*info));
memcpy(info->name, devfs_name, strlen(devfs_name));
msg->datalen = sizeof(vfs_query_info_t) + strlen(devfs_name);
return sizeof(vfs_query_info_t) + strlen(devfs_name);
}
}
break;
}
// close inbound handles so they do not leak
for (unsigned i = 0; i < ZXRIO_HC(msg->op); i++) {
zx_handle_close(msg->handle[i]);
}
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t dc_rio_handler(port_handler_t* ph, zx_signals_t signals, uint32_t evt) {
iostate_t* ios = containerof(ph, iostate_t, ph);
zx_status_t r;
zxrio_msg_t msg;
if (signals & ZX_CHANNEL_READABLE) {
if ((r = zxrio_handle_rpc(ph->handle, &msg, devfs_rio_handler, ios)) == ZX_OK) {
return ZX_OK;
}
} else if (signals & ZX_CHANNEL_PEER_CLOSED) {
zxrio_handle_close(devfs_rio_handler, ios);
r = ZX_ERR_STOP;
} else {
printf("dc_rio_handler: invalid signals %x\n", signals);
exit(0);
}
iostate_destroy(ios);
return r;
}
static zx_handle_t devfs_root;
zx_handle_t devfs_root_clone(void) {
return fdio_service_clone(devfs_root);
}
void devfs_init(zx_handle_t root_job) {
printf("devmgr: init\n");
prepopulate_protocol_dirs();
root_devnode.device = coordinator_init(root_job);
root_devnode.device->self = &root_devnode;
zx_handle_t h0, h1;
if (zx_channel_create(0, &h0, &h1) != ZX_OK) {
return;
} else if (iostate_create(&root_devnode, h0) != ZX_OK) {
zx_handle_close(h0);
zx_handle_close(h1);
return;
}
devfs_root = h1;
}