blob: acd36b78eed9871626ea3d8cf8ef04d2fec9ad7e [file] [log] [blame]
// 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 <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/protocol/block.h>
#include <zircon/device/ramdisk.h>
#include <sync/completion.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <zircon/listnode.h>
#include <sys/param.h>
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <threads.h>
typedef struct {
zx_device_t* mxdev;
} ramctl_device_t;
typedef struct ramdisk_device {
zx_device_t* mxdev;
uint64_t blk_size;
uint64_t blk_count;
zx_handle_t vmo;
uintptr_t mapped_addr;
block_callbacks_t* cb;
char name[NAME_MAX];
// Protect asynchronous operations from acting on a dead ramdisk.
// Lock not required for synchronous operations querying the
// status of 'dead'.
mtx_t lock;
bool dead;
} ramdisk_device_t;
static uint64_t sizebytes(ramdisk_device_t* rdev) {
return rdev->blk_size * rdev->blk_count;
}
static zx_status_t constrain_args(ramdisk_device_t* ramdev,
zx_off_t* offset, zx_off_t* length) {
// Offset must be aligned
if (*offset % ramdev->blk_size != 0) {
return ZX_ERR_INVALID_ARGS;
}
// Constrain to device capacity
*length = MIN(*length, sizebytes(ramdev) - *offset);
// Length must be aligned
if (*length % ramdev->blk_size != 0) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
static void ramdisk_get_info(void* ctx, block_info_t* info) {
ramdisk_device_t* ramdev = ctx;
memset(info, 0, sizeof(*info));
info->block_size = ramdev->blk_size;
info->block_count = sizebytes(ramdev) / ramdev->blk_size;
}
static void ramdisk_fifo_set_callbacks(void* ctx, block_callbacks_t* cb) {
ramdisk_device_t* rdev = ctx;
rdev->cb = cb;
}
static void ramdisk_fifo_read(void* ctx, zx_handle_t vmo, uint64_t length,
uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
ramdisk_device_t* rdev = ctx;
zx_off_t len = length;
zx_status_t status = constrain_args(rdev, &dev_offset, &len);
if (status != ZX_OK) {
rdev->cb->complete(cookie, status);
return;
}
mtx_lock(&rdev->lock);
if (rdev->dead) {
status = ZX_ERR_BAD_STATE;
} else {
size_t actual;
// Reading from disk --> Write to file VMO
status = zx_vmo_write(vmo, (void*)rdev->mapped_addr + dev_offset,
vmo_offset, len, &actual);
}
mtx_unlock(&rdev->lock);
rdev->cb->complete(cookie, status);
}
static void ramdisk_fifo_write(void* ctx, zx_handle_t vmo, uint64_t length,
uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
ramdisk_device_t* rdev = ctx;
zx_off_t len = length;
zx_status_t status = constrain_args(rdev, &dev_offset, &len);
if (status != ZX_OK) {
rdev->cb->complete(cookie, status);
return;
}
mtx_lock(&rdev->lock);
if (rdev->dead) {
status = ZX_ERR_BAD_STATE;
} else {
size_t actual = 0;
// Writing to disk --> Read from file VMO
status = zx_vmo_read(vmo, (void*)rdev->mapped_addr + dev_offset,
vmo_offset, len, &actual);
}
mtx_unlock(&rdev->lock);
rdev->cb->complete(cookie, status);
}
static block_protocol_ops_t ramdisk_block_ops = {
.set_callbacks = ramdisk_fifo_set_callbacks,
.get_info = ramdisk_get_info,
.read = ramdisk_fifo_read,
.write = ramdisk_fifo_write,
};
// implement device protocol:
static void ramdisk_unbind(void* ctx) {
ramdisk_device_t* ramdev = ctx;
mtx_lock(&ramdev->lock);
ramdev->dead = true;
mtx_unlock(&ramdev->lock);
device_remove(ramdev->mxdev);
}
static zx_status_t ramdisk_ioctl(void* ctx, uint32_t op, const void* cmd,
size_t cmdlen, void* reply, size_t max, size_t* out_actual) {
ramdisk_device_t* ramdev = ctx;
if (ramdev->dead) {
return ZX_ERR_BAD_STATE;
}
switch (op) {
case IOCTL_RAMDISK_UNLINK: {
ramdisk_unbind(ramdev);
return ZX_OK;
}
// Block Protocol
case IOCTL_BLOCK_GET_NAME: {
char* name = reply;
memset(name, 0, max);
strncpy(name, ramdev->name, max);
*out_actual = strnlen(name, max);
return ZX_OK;
}
case IOCTL_BLOCK_GET_INFO: {
block_info_t* info = reply;
if (max < sizeof(*info))
return ZX_ERR_BUFFER_TOO_SMALL;
ramdisk_get_info(ramdev, info);
*out_actual = sizeof(*info);
return ZX_OK;
}
case IOCTL_BLOCK_RR_PART: {
return device_rebind(ramdev->mxdev);
}
case IOCTL_DEVICE_SYNC: {
// Wow, we sync so quickly!
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
static void ramdisk_iotxn_queue(void* ctx, iotxn_t* txn) {
ramdisk_device_t* ramdev = ctx;
if (ramdev->dead) {
iotxn_complete(txn, ZX_ERR_BAD_STATE, 0);
return;
}
zx_status_t status = constrain_args(ramdev, &txn->offset, &txn->length);
if (status != ZX_OK) {
iotxn_complete(txn, status, 0);
return;
}
switch (txn->opcode) {
case IOTXN_OP_READ: {
iotxn_copyto(txn, (void*) ramdev->mapped_addr + txn->offset, txn->length, 0);
iotxn_complete(txn, ZX_OK, txn->length);
return;
}
case IOTXN_OP_WRITE: {
iotxn_copyfrom(txn, (void*) ramdev->mapped_addr + txn->offset, txn->length, 0);
iotxn_complete(txn, ZX_OK, txn->length);
return;
}
default: {
iotxn_complete(txn, ZX_ERR_INVALID_ARGS, 0);
return;
}
}
}
static zx_off_t ramdisk_getsize(void* ctx) {
return sizebytes(ctx);
}
static void ramdisk_release(void* ctx) {
ramdisk_device_t* ramdev = ctx;
if (ramdev->vmo != ZX_HANDLE_INVALID) {
zx_vmar_unmap(zx_vmar_root_self(), ramdev->mapped_addr, sizebytes(ramdev));
zx_handle_close(ramdev->vmo);
}
free(ramdev);
}
static zx_protocol_device_t ramdisk_instance_proto = {
.version = DEVICE_OPS_VERSION,
.ioctl = ramdisk_ioctl,
.iotxn_queue = ramdisk_iotxn_queue,
.get_size = ramdisk_getsize,
.unbind = ramdisk_unbind,
.release = ramdisk_release,
};
// implement device protocol:
static uint64_t ramdisk_count = 0;
static zx_status_t ramctl_ioctl(void* ctx, uint32_t op, const void* cmd,
size_t cmdlen, void* reply, size_t max, size_t* out_actual) {
ramctl_device_t* ramctl = ctx;
switch (op) {
case IOCTL_RAMDISK_CONFIG: {
if (cmdlen != sizeof(ramdisk_ioctl_config_t)) {
return ZX_ERR_INVALID_ARGS;
}
if (max < 32) {
return ZX_ERR_INVALID_ARGS;
}
ramdisk_ioctl_config_t* config = (ramdisk_ioctl_config_t*)cmd;
ramdisk_device_t* ramdev = calloc(1, sizeof(ramdisk_device_t));
if (!ramdev) {
return ZX_ERR_NO_MEMORY;
}
ramdev->blk_size = config->blk_size;
ramdev->blk_count = config->blk_count;
mtx_init(&ramdev->lock, mtx_plain);
sprintf(ramdev->name, "ramdisk-%lu", ramdisk_count++);
zx_status_t status;
if ((status = zx_vmo_create(sizebytes(ramdev), 0, &ramdev->vmo)) != ZX_OK) {
free(ramdev);
return status;
}
if ((status = zx_vmar_map(zx_vmar_root_self(), 0, ramdev->vmo, 0, sizebytes(ramdev),
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE,
&ramdev->mapped_addr)) != ZX_OK) {
zx_handle_close(ramdev->vmo);
free(ramdev);
return status;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = ramdev->name,
.ctx = ramdev,
.ops = &ramdisk_instance_proto,
.proto_id = ZX_PROTOCOL_BLOCK_CORE,
.proto_ops = &ramdisk_block_ops,
};
if ((status = device_add(ramctl->mxdev, &args, &ramdev->mxdev)) != ZX_OK) {
zx_vmar_unmap(zx_vmar_root_self(), ramdev->mapped_addr, sizebytes(ramdev));
zx_handle_close(ramdev->vmo);
free(ramdev);
return status;
}
strcpy(reply, ramdev->name);
*out_actual = strlen(reply);
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
static zx_protocol_device_t ramdisk_ctl_proto = {
.version = DEVICE_OPS_VERSION,
.ioctl = ramctl_ioctl,
};
static zx_status_t ramdisk_driver_bind(void* ctx, zx_device_t* parent, void** cookie) {
ramctl_device_t* ramctl = calloc(1, sizeof(ramctl_device_t));
if (ramctl == NULL) {
return ZX_ERR_NO_MEMORY;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "ramctl",
.ops = &ramdisk_ctl_proto,
.ctx = ramctl,
};
return device_add(parent, &args, &ramctl->mxdev);
}
static zx_driver_ops_t ramdisk_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = ramdisk_driver_bind,
};
ZIRCON_DRIVER_BEGIN(ramdisk, ramdisk_driver_ops, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
ZIRCON_DRIVER_END(ramdisk)