blob: 0e1eabab20150033d2a97fa81761ddc7f863c7a9 [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 "devhost.h"
#include "device-internal.h"
#include <assert.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <sync/completion.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <zircon/device/device.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <fdio/debug.h>
#include <fdio/io.fidl2.h>
#include <fdio/io.h>
#include <fdio/vfs.h>
#define ZXDEBUG 0
#define CAN_WRITE(ios) (ios->flags & ZX_FS_RIGHT_WRITABLE)
#define CAN_READ(ios) (ios->flags & ZX_FS_RIGHT_READABLE)
void describe_error(zx_handle_t h, zx_status_t status) {
zxrio_describe_t msg;
memset(&msg, 0, sizeof(msg));
msg.op = ZXRIO_ON_OPEN;
msg.status = status;
zx_channel_write(h, 0, &msg, sizeof(msg), NULL, 0);
zx_handle_close(h);
}
static zx_status_t create_description(zx_device_t* dev, zxrio_describe_t* msg,
zx_handle_t* handle) {
memset(msg, 0, sizeof(*msg));
msg->op = ZXRIO_ON_OPEN;
msg->extra.tag = FDIO_PROTOCOL_DEVICE;
msg->status = ZX_OK;
*handle = ZX_HANDLE_INVALID;
if (dev->event != ZX_HANDLE_INVALID) {
//TODO: read only?
zx_status_t r;
if ((r = zx_handle_duplicate(dev->event, ZX_RIGHT_SAME_RIGHTS,
handle)) != ZX_OK) {
msg->status = r;
return r;
}
}
return ZX_OK;
}
devhost_iostate_t* create_devhost_iostate(zx_device_t* dev) {
devhost_iostate_t* ios;
if ((ios = calloc(1, sizeof(devhost_iostate_t))) == NULL) {
return NULL;
}
ios->dev = dev;
return ios;
}
static zx_status_t devhost_get_handles(zx_handle_t rh, zx_device_t* dev,
const char* path, uint32_t flags) {
zx_status_t r;
devhost_iostate_t* newios;
// detect response directives and discard all other
// protocol flags
bool describe = flags & ZX_FS_FLAG_DESCRIBE;
flags &= (~ZX_FS_FLAG_DESCRIBE);
if ((newios = create_devhost_iostate(dev)) == NULL) {
if (describe) {
describe_error(rh, ZX_ERR_NO_MEMORY);
}
return ZX_ERR_NO_MEMORY;
}
newios->flags = flags;
if ((r = device_open_at(dev, &dev, path, flags)) < 0) {
fprintf(stderr, "devhost_get_handles(%p:%s) open path='%s', r=%d\n",
dev, dev->name, path ? path : "", r);
goto fail;
}
newios->dev = dev;
if (describe) {
zxrio_describe_t info;
zx_handle_t handle;
if ((r = create_description(dev, &info, &handle)) != ZX_OK) {
goto fail_open;
}
uint32_t hcount = (handle != ZX_HANDLE_INVALID) ? 1 : 0;
r = zx_channel_write(rh, 0, &info, sizeof(info), &handle, hcount);
if (r != ZX_OK) {
if (hcount) {
zx_handle_close(handle);
}
goto fail_open;
}
}
// If we can't add the new ios and handle to the dispatcher our only option
// is to give up and tear down. In practice, this should never happen.
if ((r = devhost_start_iostate(newios, rh)) < 0) {
fprintf(stderr, "devhost_get_handles: failed to start iostate\n");
goto fail;
}
return ZX_OK;
fail_open:
device_close(dev, flags);
fail:
free(newios);
if (describe) {
describe_error(rh, r);
} else {
zx_handle_close(rh);
}
return r;
}
#define DO_READ 0
#define DO_WRITE 1
static ssize_t do_sync_io(zx_device_t* dev, uint32_t opcode, void* buf, size_t count, zx_off_t off) {
size_t actual;
zx_status_t r;
if (opcode == DO_READ) {
r = dev_op_read(dev, buf, count, off, &actual);
} else {
r = dev_op_write(dev, buf, count, off, &actual);
}
if (r < 0) {
return r;
} else {
return actual;
}
}
static ssize_t do_ioctl(zx_device_t* dev, uint32_t op, const void* in_buf, size_t in_len, void* out_buf, size_t out_len) {
zx_status_t r;
switch (op) {
case IOCTL_DEVICE_BIND: {
char* drv_libname = in_len > 0 ? (char*)in_buf : NULL;
if (in_len > PATH_MAX) {
return ZX_ERR_BAD_PATH;
}
drv_libname[in_len] = 0;
return device_bind(dev, drv_libname);
}
case IOCTL_DEVICE_GET_EVENT_HANDLE: {
if (out_len < sizeof(zx_handle_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
zx_handle_t* event = out_buf;
r = zx_handle_duplicate(dev->event, ZX_RIGHTS_BASIC | ZX_RIGHT_READ, event);
if (r == ZX_OK) {
r = sizeof(zx_handle_t);
}
return r;
}
case IOCTL_DEVICE_GET_DRIVER_NAME: {
if (!dev->driver) {
return ZX_ERR_NOT_SUPPORTED;
}
const char* name = dev->driver->name;
if (name == NULL) {
name = "unknown";
}
r = strlen(name);
if (out_len < (size_t)r) {
r = ZX_ERR_BUFFER_TOO_SMALL;
} else {
strncpy(out_buf, name, r);
}
return r;
}
case IOCTL_DEVICE_GET_DEVICE_NAME: {
r = strlen(dev->name);
if (out_len < (size_t)r) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
strncpy(out_buf, dev->name, r);
return r;
}
case IOCTL_DEVICE_GET_TOPO_PATH: {
size_t actual;
if ((r = devhost_get_topo_path(dev, out_buf, out_len, &actual)) < 0) {
return r;
}
return actual;
}
case IOCTL_DEVICE_DEBUG_SUSPEND: {
return dev_op_suspend(dev, 0);
}
case IOCTL_DEVICE_DEBUG_RESUME: {
return dev_op_resume(dev, 0);
}
case IOCTL_VFS_QUERY_FS: {
const char* devhost_name = "devfs:host";
if (out_len < sizeof(vfs_query_info_t) + strlen(devhost_name)) {
return ZX_ERR_INVALID_ARGS;
}
vfs_query_info_t* info = (vfs_query_info_t*) out_buf;
memset(info, 0, sizeof(*info));
memcpy(info->name, devhost_name, strlen(devhost_name));
return sizeof(vfs_query_info_t) + strlen(devhost_name);
}
case IOCTL_DEVICE_GET_DRIVER_LOG_FLAGS: {
if (!dev->driver) {
return ZX_ERR_UNAVAILABLE;
}
if (out_len < sizeof(uint32_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
*((uint32_t *)out_buf) = dev->driver->driver_rec->log_flags;
return sizeof(uint32_t);
}
case IOCTL_DEVICE_SET_DRIVER_LOG_FLAGS: {
if (!dev->driver) {
return ZX_ERR_UNAVAILABLE;
}
if (in_len < sizeof(driver_log_flags_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
driver_log_flags_t* flags = (driver_log_flags_t *)in_buf;
dev->driver->driver_rec->log_flags &= ~flags->clear;
dev->driver->driver_rec->log_flags |= flags->set;
return sizeof(driver_log_flags_t);
}
default: {
size_t actual = 0;
r = dev_op_ioctl(dev, op, in_buf, in_len, out_buf, out_len, &actual);
if (r == ZX_OK) {
r = actual;
}
}
}
return r;
}
static void discard_handles(zx_handle_t* handles, size_t count) {
while (count-- > 0) {
zx_handle_close(*handles++);
}
}
zx_status_t devhost_rio_handler(zxrio_msg_t* msg, void* cookie) {
devhost_iostate_t* ios = cookie;
zx_device_t* dev = ios->dev;
uint32_t len = msg->datalen;
int32_t arg = msg->arg;
if (!ZXRIO_FIDL_MSG(msg->op)) {
msg->datalen = 0;
msg->hcount = 0;
}
switch (ZXRIO_OP(msg->op)) {
case ZXFIDL_CLOSE:
case ZXRIO_CLOSE:
device_close(dev, ios->flags);
// The ios released its reference to this device by calling device_close()
// Put an invalid pointer in its dev field to ensure any use-after-release
// attempts explode.
ios->dev = (void*) 0xdead;
return ZX_OK;
case ZXFIDL_OPEN:
case ZXRIO_OPEN: {
bool fidl = ZXRIO_FIDL_MSG(msg->op);
DirectoryOpenRequest* request = (DirectoryOpenRequest*) msg;
zx_handle_t h;
char* name;
uint32_t flags;
if (fidl) {
len = request->path.size;
h = request->object;
name = request->path.data;
flags = request->flags;
} else {
h = msg->handle[0];
name = (char*) msg->data;
flags = arg;
}
if ((len < 1) || (len > 1024)) {
zx_handle_close(h);
return ERR_DISPATCHER_INDIRECT;
}
name[len] = 0;
if (!strcmp(name, ".")) {
name = NULL;
}
devhost_get_handles(h, dev, name, flags);
return ERR_DISPATCHER_INDIRECT;
}
case ZXFIDL_CLONE:
case ZXRIO_CLONE: {
bool fidl = ZXRIO_FIDL_MSG(msg->op);
ObjectCloneRequest* request = (ObjectCloneRequest*) msg;
zx_handle_t h;
uint32_t flags;
if (fidl) {
h = request->object;
flags = request->flags;
} else {
h = msg->handle[0];
flags = arg;
}
flags = ios->flags | (flags & ZX_FS_FLAG_DESCRIBE);
devhost_get_handles(h, dev, NULL, flags);
return ERR_DISPATCHER_INDIRECT;
}
case ZXFIDL_READ:
case ZXRIO_READ: {
if (!CAN_READ(ios)) {
return ZX_ERR_ACCESS_DENIED;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileReadRequest* request = (FileReadRequest*) msg;
FileReadResponse* response = (FileReadResponse*) msg;
void* data;
if (fidl) {
data = (void*)((uintptr_t)(response) + FIDL_ALIGN(sizeof(FileReadResponse)));
len = request->count;
} else {
data = msg->data;
len = arg;
}
zx_status_t r = do_sync_io(dev, DO_READ, data, len, ios->io_off);
if (r >= 0) {
ios->io_off += r;
if (fidl) {
response->data.count = r;
r = ZX_OK;
} else {
msg->datalen = r;
}
}
return r;
}
case ZXFIDL_READ_AT:
case ZXRIO_READ_AT: {
if (!CAN_READ(ios)) {
return ZX_ERR_ACCESS_DENIED;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileReadAtRequest* request = (FileReadAtRequest*) msg;
FileReadAtResponse* response = (FileReadAtResponse*) msg;
void* data;
uint64_t offset;
if (fidl) {
data = (void*)((uintptr_t)(response) + FIDL_ALIGN(sizeof(FileReadAtResponse)));
len = request->count;
offset = request->offset;
} else {
data = msg->data;
len = arg;
offset = msg->arg2.off;
}
zx_status_t r = do_sync_io(dev, DO_READ, data, len, offset);
if (fidl) {
response->data.count = r;
return r > 0 ? ZX_OK : r;
} else {
msg->datalen = r;
return r;
}
}
case ZXFIDL_WRITE:
case ZXRIO_WRITE: {
if (!CAN_WRITE(ios)) {
return ZX_ERR_ACCESS_DENIED;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileWriteRequest* request = (FileWriteRequest*) msg;
FileWriteResponse* response = (FileWriteResponse*) msg;
void* data;
if (fidl) {
data = request->data.data;
len = request->data.count;
} else {
data = msg->data;
}
zx_status_t r = do_sync_io(dev, DO_WRITE, data, len, ios->io_off);
if (r >= 0) {
ios->io_off += r;
if (fidl) {
response->actual = r;
r = ZX_OK;
}
}
return r;
}
case ZXFIDL_WRITE_AT:
case ZXRIO_WRITE_AT: {
if (!CAN_WRITE(ios)) {
return ZX_ERR_ACCESS_DENIED;
}
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileWriteAtRequest* request = (FileWriteAtRequest*) msg;
FileWriteAtResponse* response = (FileWriteAtResponse*) msg;
void* data;
uint64_t offset;
if (fidl) {
data = request->data.data;
len = request->data.count;
offset = request->offset;
} else {
data = msg->data;
offset = msg->arg2.off;
}
zx_status_t r = do_sync_io(dev, DO_WRITE, data, len, offset);
if (fidl) {
response->actual = r > 0 ? r : 0;
return r > 0 ? ZX_OK : r;
} else {
return r;
}
}
case ZXFIDL_SEEK:
case ZXRIO_SEEK: {
bool fidl = ZXRIO_FIDL_MSG(msg->op);
FileSeekRequest* request = (FileSeekRequest*) msg;
FileSeekResponse* response = (FileSeekResponse*) msg;
static_assert(SEEK_SET == SeekOrigin_Start, "");
static_assert(SEEK_CUR == SeekOrigin_Current, "");
static_assert(SEEK_END == SeekOrigin_End, "");
off_t offset;
int whence;
if (fidl) {
offset = request->offset;
whence = request->start;
} else {
offset = msg->arg2.off;
whence = arg;
}
size_t end, n;
end = dev_op_get_size(dev);
switch (whence) {
case SEEK_SET:
if ((offset < 0) || ((size_t)offset > end)) {
return ZX_ERR_INVALID_ARGS;
}
n = offset;
break;
case SEEK_CUR:
// TODO: track seekability with flag, don't update off
// at all on read/write if not seekable
n = ios->io_off + offset;
if (offset < 0) {
// if negative seek
if (n > ios->io_off) {
// wrapped around
return ZX_ERR_INVALID_ARGS;
}
} else {
// positive seek
if (n < ios->io_off) {
// wrapped around
return ZX_ERR_INVALID_ARGS;
}
}
break;
case SEEK_END:
n = end + offset;
if (offset <= 0) {
// if negative or exact-end seek
if (n > end) {
// wrapped around
return ZX_ERR_INVALID_ARGS;
}
} else {
if (n < end) {
// wrapped around
return ZX_ERR_INVALID_ARGS;
}
}
break;
default:
return ZX_ERR_INVALID_ARGS;
}
if (n > end) {
// devices may not seek past the end
return ZX_ERR_INVALID_ARGS;
}
ios->io_off = n;
if (fidl) {
response->offset = ios->io_off;
} else {
msg->arg2.off = ios->io_off;
}
return ZX_OK;
}
case ZXFIDL_STAT:
case ZXRIO_STAT: {
bool fidl = ZXRIO_FIDL_MSG(msg->op);
NodeGetAttrResponse* response = (NodeGetAttrResponse*) msg;
if (fidl) {
memset(&response->attributes, 0, sizeof(response->attributes));
response->attributes.mode = V_TYPE_CDEV | V_IRUSR | V_IWUSR;
response->attributes.content_size = dev_op_get_size(dev);
response->attributes.link_count = 1;
return ZX_OK;
}
msg->datalen = sizeof(vnattr_t);
vnattr_t* attr = (void*)msg->data;
memset(attr, 0, sizeof(vnattr_t));
attr->mode = V_TYPE_CDEV | V_IRUSR | V_IWUSR;
attr->size = dev_op_get_size(dev);
attr->nlink = 1;
return msg->datalen;
}
case ZXFIDL_SYNC:
case ZXRIO_SYNC: {
return do_ioctl(dev, IOCTL_DEVICE_SYNC, NULL, 0, NULL, 0);
}
case ZXFIDL_IOCTL: {
NodeIoctlRequest* request = (NodeIoctlRequest*) msg;
NodeIoctlResponse* response = (NodeIoctlResponse*) msg;
char in_buf[FDIO_IOCTL_MAX_INPUT];
size_t hsize = request->handles.count * sizeof(zx_handle_t);
if (hsize + request->in.count > FDIO_IOCTL_MAX_INPUT) {
discard_handles(request->handles.data, request->handles.count);
return ZX_ERR_INVALID_ARGS;
}
memcpy(in_buf, request->in.data, request->in.count);
memcpy(in_buf, request->handles.data, hsize);
uint32_t op = request->opcode;
void* secondary = (void*)((uintptr_t)(msg) + FIDL_ALIGN(sizeof(NodeIoctlResponse)));
zx_status_t r = do_ioctl(dev, op, in_buf, request->in.count,
secondary, request->max_out);
if (r >= 0) {
response->out.count = r;
r = ZX_OK;
switch (IOCTL_KIND(op)) {
case IOCTL_KIND_GET_HANDLE:
response->handles.count = 1;
break;
case IOCTL_KIND_GET_TWO_HANDLES:
response->handles.count = 2;
break;
case IOCTL_KIND_GET_THREE_HANDLES:
response->handles.count = 3;
break;
default:
response->handles.count = 0;
break;
}
}
response->handles.data = secondary;
response->out.data = secondary;
return r;
}
case ZXRIO_IOCTL_2H: {
if ((len > FDIO_IOCTL_MAX_INPUT) ||
(arg > (ssize_t)sizeof(msg->data)) ||
(IOCTL_KIND(msg->arg2.op) != IOCTL_KIND_SET_TWO_HANDLES)) {
zx_handle_close(msg->handle[0]);
zx_handle_close(msg->handle[1]);
return ZX_ERR_INVALID_ARGS;
}
size_t hsize = 2 * sizeof(zx_handle_t);
if (len < hsize) {
len = hsize;
}
char in_buf[FDIO_IOCTL_MAX_INPUT];
memcpy(in_buf, msg->handle, hsize);
memcpy(in_buf + hsize, msg->data + hsize, len - hsize);
zx_status_t r = do_ioctl(dev, msg->arg2.op, in_buf, len, msg->data, arg);
if (r == ZX_ERR_NOT_SUPPORTED) {
zx_handle_close(msg->handle[0]);
zx_handle_close(msg->handle[1]);
} else if (r >= 0) {
msg->datalen = r;
}
return r;
}
case ZXRIO_IOCTL_1H: {
if ((len > FDIO_IOCTL_MAX_INPUT) ||
(arg > (ssize_t)sizeof(msg->data)) ||
(IOCTL_KIND(msg->arg2.op) != IOCTL_KIND_SET_HANDLE)) {
zx_handle_close(msg->handle[0]);
return ZX_ERR_INVALID_ARGS;
}
if (len < sizeof(zx_handle_t)) {
len = sizeof(zx_handle_t);
}
char in_buf[FDIO_IOCTL_MAX_INPUT];
// The sending side copied the handle into msg->handle[0]
// so that it would be sent via channel_write(). Here we
// copy the local version back into the space in the buffer
// that the original occupied.
memcpy(in_buf, msg->handle, sizeof(zx_handle_t));
memcpy(in_buf + sizeof(zx_handle_t), msg->data + sizeof(zx_handle_t),
len - sizeof(zx_handle_t));
zx_status_t r = do_ioctl(dev, msg->arg2.op, in_buf, len, msg->data, arg);
if (r == ZX_ERR_NOT_SUPPORTED) {
zx_handle_close(msg->handle[0]);
} else if (r >= 0) {
msg->datalen = r;
}
return r;
}
case ZXRIO_IOCTL: {
if ((len > FDIO_IOCTL_MAX_INPUT) ||
(arg > (ssize_t)sizeof(msg->data)) ||
(IOCTL_KIND(msg->arg2.op) == IOCTL_KIND_SET_HANDLE)) {
return ZX_ERR_INVALID_ARGS;
}
char in_buf[FDIO_IOCTL_MAX_INPUT];
memcpy(in_buf, msg->data, len);
zx_status_t r = do_ioctl(dev, msg->arg2.op, in_buf, len, msg->data, arg);
if (r >= 0) {
switch (IOCTL_KIND(msg->arg2.op)) {
case IOCTL_KIND_GET_HANDLE:
msg->hcount = 1;
memcpy(msg->handle, msg->data, sizeof(zx_handle_t));
break;
case IOCTL_KIND_GET_TWO_HANDLES:
msg->hcount = 2;
memcpy(msg->handle, msg->data, 2 * sizeof(zx_handle_t));
break;
case IOCTL_KIND_GET_THREE_HANDLES:
msg->hcount = 3;
memcpy(msg->handle, msg->data, 3 * sizeof(zx_handle_t));
break;
}
msg->datalen = r;
msg->arg2.off = ios->io_off;
}
return r;
}
default:
// 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;
}
}