summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/md/bcache/Makefile8
-rw-r--r--drivers/md/bcache/bcache.h5
-rw-r--r--drivers/md/bcache/chardev.c279
-rw-r--r--drivers/md/bcache/super.c48
-rw-r--r--include/uapi/linux/bcache-ioctl.h80
5 files changed, 416 insertions, 4 deletions
diff --git a/drivers/md/bcache/Makefile b/drivers/md/bcache/Makefile
index 0dd3db8a5ef4..650d6542eb5a 100644
--- a/drivers/md/bcache/Makefile
+++ b/drivers/md/bcache/Makefile
@@ -2,9 +2,9 @@
obj-$(CONFIG_BCACHE) += bcache.o
bcache-y := acl.o alloc.o bkey.o bkey_methods.o blockdev.o\
- bset.o btree.o buckets.o clock.o closure.o debug.o dirent.o extents.o\
- fs.o fs-gc.o gc.o inode.o io.o journal.o keybuf.o keylist.o migrate.o\
- move.o movinggc.o notify.o request.o siphash.o six.o stats.o super.o\
- sysfs.o tier.o trace.o util.o writeback.o xattr.o
+ bset.o btree.o buckets.o chardev.o clock.o closure.o debug.o dirent.o\
+ extents.o fs.o fs-gc.o gc.o inode.o io.o journal.o keybuf.o keylist.o\
+ migrate.o move.o movinggc.o notify.o request.o siphash.o six.o stats.o\
+ super.o sysfs.o tier.o trace.o util.o writeback.o xattr.o
ccflags-y := -Werror
diff --git a/drivers/md/bcache/bcache.h b/drivers/md/bcache/bcache.h
index b203e28c48ca..55019efd6909 100644
--- a/drivers/md/bcache/bcache.h
+++ b/drivers/md/bcache/bcache.h
@@ -409,6 +409,9 @@ struct cache_set {
struct completion *stop_completion;
unsigned long flags;
+ int minor;
+ struct device *chardev;
+
/* Counts outstanding writes, for clean transition to read-only */
struct percpu_ref writes;
struct completion write_disable_complete;
@@ -771,6 +774,8 @@ do { \
/* Forward declarations */
+long bch_chardev_ioctl(struct file *, unsigned, unsigned long);
+
void bch_debug_exit(void);
int bch_debug_init(void);
void bch_fs_exit(void);
diff --git a/drivers/md/bcache/chardev.c b/drivers/md/bcache/chardev.c
new file mode 100644
index 000000000000..cffba0dc937d
--- /dev/null
+++ b/drivers/md/bcache/chardev.c
@@ -0,0 +1,279 @@
+/*
+ * This file adds support for a character device /dev/bcache that is used to
+ * atomically register a list of devices, remove a device from a cache_set
+ * and add a device to a cache set.
+ *
+ * Copyright (c) 2014 Datera, Inc.
+ *
+ */
+
+#include "bcache.h"
+#include "super.h"
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/bcache-ioctl.h>
+
+static long bch_ioctl_assemble(struct bch_ioctl_assemble __user *user_arg)
+{
+ struct bch_ioctl_assemble arg;
+ const char *err;
+ u64 *user_devs = NULL;
+ char **devs = NULL;
+ unsigned i;
+ int ret = -EFAULT;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ user_devs = kmalloc_array(arg.nr_devs, sizeof(u64), GFP_KERNEL);
+ if (!devs)
+ return -ENOMEM;
+
+ devs = kcalloc(arg.nr_devs, sizeof(char *), GFP_KERNEL);
+
+ if (copy_from_user(user_devs, user_arg->devs,
+ sizeof(u64) * arg.nr_devs))
+ goto err;
+
+ for (i = 0; i < arg.nr_devs; i++) {
+ devs[i] = strndup_user((const char __user *)(unsigned long)
+ user_devs[i],
+ PATH_MAX);
+ if (!devs[i]) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ }
+
+ err = bch_register_cache_set(devs, arg.nr_devs, NULL);
+ if (err) {
+ pr_err("Could not register cache set: %s", err);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = 0;
+err:
+ if (devs)
+ for (i = 0; i < arg.nr_devs; i++)
+ kfree(devs[i]);
+ kfree(devs);
+ return ret;
+}
+
+static long bch_ioctl_incremental(struct bch_ioctl_incremental __user *user_arg)
+{
+ struct bch_ioctl_incremental arg;
+ const char *err;
+ char *path;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
+ if (!path)
+ return -ENOMEM;
+
+ err = bch_register_one(path);
+ kfree(path);
+
+ if (err) {
+ pr_err("Could not register bcache devices: %s", err);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static long bch_ioctl_stop(struct cache_set *c)
+{
+ bch_cache_set_stop(c);
+ return 0;
+}
+
+static long bch_ioctl_disk_add(struct cache_set *c,
+ struct bch_ioctl_disk_add __user *user_arg)
+{
+ struct bch_ioctl_disk_add arg;
+ char *path;
+ int ret;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ path = strndup_user((const char __user *)(unsigned long) arg.dev, PATH_MAX);
+ if (!path)
+ return -ENOMEM;
+
+ ret = bch_cache_set_add_cache(c, path);
+ kfree(path);
+
+ return ret;
+}
+
+/* returns with ref on ca->ref */
+static struct cache *bch_device_lookup(struct cache_set *c,
+ const char __user *dev)
+{
+ struct block_device *bdev;
+ struct cache *ca;
+ char *path;
+ unsigned i;
+
+ path = strndup_user(dev, PATH_MAX);
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+
+ bdev = lookup_bdev(strim(path));
+ kfree(path);
+ if (IS_ERR(bdev))
+ return ERR_CAST(bdev);
+
+ for_each_cache(ca, c, i)
+ if (ca->disk_sb.bdev == bdev)
+ goto found;
+
+ ca = NULL;
+found:
+ bdput(bdev);
+ return ca;
+}
+
+static long bch_ioctl_disk_remove(struct cache_set *c,
+ struct bch_ioctl_disk_remove __user *user_arg)
+{
+ struct bch_ioctl_disk_remove arg;
+ struct cache *ca;
+ int ret;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ ca = bch_device_lookup(c, (const char __user *)(unsigned long) arg.dev);
+ if (IS_ERR(ca))
+ return PTR_ERR(ca);
+
+ ret = bch_cache_remove(ca, arg.flags & BCH_FORCE_IF_DATA_MISSING)
+ ? 0 : -EBUSY;
+
+ percpu_ref_put(&ca->ref);
+ return ret;
+}
+
+static long bch_ioctl_disk_fail(struct cache_set *c,
+ struct bch_ioctl_disk_fail __user *user_arg)
+{
+ struct bch_ioctl_disk_fail arg;
+ struct cache *ca;
+ int ret;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ ca = bch_device_lookup(c, (const char __user *)(unsigned long) arg.dev);
+ if (IS_ERR(ca))
+ return PTR_ERR(ca);
+
+ /* XXX: failed not actually implemented yet */
+ ret = bch_cache_remove(ca, true);
+
+ percpu_ref_put(&ca->ref);
+ return ret;
+}
+
+static struct cache_member *bch_uuid_lookup(struct cache_set *c, uuid_le uuid)
+{
+ struct cache_member_rcu *mi =
+ rcu_dereference_protected(c->members,
+ lockdep_is_held(&bch_register_lock));
+ unsigned i;
+
+ for (i = 0; i < mi->nr_in_set; i++)
+ if (!memcmp(&mi->m[i].uuid, &uuid, sizeof(uuid)))
+ return &mi->m[i];
+
+ return NULL;
+}
+
+static long bch_ioctl_disk_remove_by_uuid(struct cache_set *c,
+ struct bch_ioctl_disk_remove_by_uuid __user *user_arg)
+{
+ struct bch_ioctl_disk_fail_by_uuid arg;
+ struct cache_member *m;
+ int ret = -ENOENT;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ mutex_lock(&bch_register_lock);
+ if ((m = bch_uuid_lookup(c, arg.dev))) {
+ /* XXX: */
+ SET_CACHE_STATE(m, CACHE_FAILED);
+ bcache_write_super(c);
+ ret = 0;
+ }
+ mutex_unlock(&bch_register_lock);
+
+ return ret;
+}
+
+static long bch_ioctl_disk_fail_by_uuid(struct cache_set *c,
+ struct bch_ioctl_disk_fail_by_uuid __user *user_arg)
+{
+ struct bch_ioctl_disk_fail_by_uuid arg;
+ struct cache_member *m;
+ int ret = -ENOENT;
+
+ if (copy_from_user(&arg, user_arg, sizeof(arg)))
+ return -EFAULT;
+
+ mutex_lock(&bch_register_lock);
+ if ((m = bch_uuid_lookup(c, arg.dev))) {
+ SET_CACHE_STATE(m, CACHE_FAILED);
+ bcache_write_super(c);
+ ret = 0;
+ }
+ mutex_unlock(&bch_register_lock);
+
+ return ret;
+}
+
+long bch_chardev_ioctl(struct file *filp, unsigned cmd, unsigned long v)
+{
+ struct cache_set *c = filp->private_data;
+ void __user *arg = (void __user *) v;
+
+ switch (cmd) {
+ case BCH_IOCTL_ASSEMBLE:
+ return bch_ioctl_assemble(arg);
+ case BCH_IOCTL_INCREMENTAL:
+ return bch_ioctl_incremental(arg);
+
+ case BCH_IOCTL_RUN:
+ return -ENOTTY;
+ case BCH_IOCTL_STOP:
+ return bch_ioctl_stop(c);
+
+ case BCH_IOCTL_DISK_ADD:
+ return bch_ioctl_disk_add(c, arg);
+ case BCH_IOCTL_DISK_REMOVE:
+ return bch_ioctl_disk_remove(c, arg);
+ case BCH_IOCTL_DISK_FAIL:
+ return bch_ioctl_disk_fail(c, arg);
+
+ case BCH_IOCTL_DISK_REMOVE_BY_UUID:
+ return bch_ioctl_disk_remove_by_uuid(c, arg);
+ case BCH_IOCTL_DISK_FAIL_BY_UUID:
+ return bch_ioctl_disk_fail_by_uuid(c, arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
diff --git a/drivers/md/bcache/super.c b/drivers/md/bcache/super.c
index 25e570253b1b..b35ff5a86a33 100644
--- a/drivers/md/bcache/super.c
+++ b/drivers/md/bcache/super.c
@@ -55,6 +55,11 @@ static struct kset *bcache_kset;
struct mutex bch_register_lock;
LIST_HEAD(bch_cache_sets);
+static int bch_chardev_major;
+static struct class *bch_chardev_class;
+static struct device *bch_chardev;
+static DEFINE_IDR(bch_chardev_minor);
+
struct workqueue_struct *bcache_io_wq;
static void bch_cache_stop(struct cache *);
@@ -840,6 +845,8 @@ static void cache_set_free(struct closure *cl)
mutex_lock(&bch_register_lock);
list_del(&c->list);
+ if (c->minor >= 0)
+ idr_remove(&bch_chardev_minor, c->minor);
mutex_unlock(&bch_register_lock);
bch_notify_cache_set_stopped(c);
@@ -854,6 +861,9 @@ static void cache_set_flush(struct closure *cl)
{
struct cache_set *c = container_of(cl, struct cache_set, caching);
+ if (!IS_ERR_OR_NULL(c->chardev))
+ device_unregister(c->chardev);
+
mutex_lock(&bch_register_lock);
bch_cache_set_read_only(c);
@@ -978,6 +988,7 @@ static struct cache_set *bch_cache_set_alloc(struct cache_sb *sb)
if (cache_sb_to_cache_set(c, sb))
goto err;
+ c->minor = -1;
c->block_bits = ilog2(c->sb.block_size);
sema_init(&c->sb_write_mutex, 1);
@@ -1079,6 +1090,16 @@ static int bch_cache_set_online(struct cache_set *c)
lockdep_assert_held(&bch_register_lock);
+ c->minor = idr_alloc(&bch_chardev_minor, c, 0, 0, GFP_KERNEL);
+ if (c->minor < 0)
+ return c->minor;
+
+ c->chardev = device_create(bch_chardev_class, NULL,
+ MKDEV(bch_chardev_major, c->minor), NULL,
+ "bcache%u-ctl", c->minor);
+ if (IS_ERR(c->chardev))
+ return PTR_ERR(c->chardev);
+
if (kobject_add(&c->kobj, NULL, "%pU", c->sb.user_uuid.b) ||
kobject_add(&c->internal, &c->kobj, "internal") ||
bch_cache_accounting_add_kobjs(&c->accounting, &c->kobj))
@@ -2424,9 +2445,22 @@ static void bcache_exit(void)
kset_unregister(bcache_kset);
if (bcache_io_wq)
destroy_workqueue(bcache_io_wq);
+ if (!IS_ERR_OR_NULL(bch_chardev_class))
+ device_destroy(bch_chardev_class,
+ MKDEV(bch_chardev_major, 0));
+ if (!IS_ERR_OR_NULL(bch_chardev_class))
+ class_destroy(bch_chardev_class);
+ if (bch_chardev_major > 0)
+ unregister_chrdev(bch_chardev_major, "bcache");
unregister_reboot_notifier(&reboot);
}
+static const struct file_operations bch_chardev_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = bch_chardev_ioctl,
+ .open = nonseekable_open,
+};
+
static int __init bcache_init(void)
{
static const struct attribute *files[] = {
@@ -2441,6 +2475,20 @@ static int __init bcache_init(void)
closure_debug_init();
bkey_pack_test();
+ bch_chardev_major = register_chrdev(0, "bcache-ctl", &bch_chardev_fops);
+ if (bch_chardev_major < 0)
+ goto err;
+
+ bch_chardev_class = class_create(THIS_MODULE, "bcache");
+ if (IS_ERR(bch_chardev_class))
+ goto err;
+
+ bch_chardev = device_create(bch_chardev_class, NULL,
+ MKDEV(bch_chardev_major, 255),
+ NULL, "bcache-ctl");
+ if (IS_ERR(bch_chardev))
+ goto err;
+
if (!(bcache_io_wq = alloc_workqueue("bcache_io", WQ_MEM_RECLAIM, 0)) ||
!(bcache_kset = kset_create_and_add("bcache", NULL, fs_kobj)) ||
sysfs_create_files(&bcache_kset->kobj, files) ||
diff --git a/include/uapi/linux/bcache-ioctl.h b/include/uapi/linux/bcache-ioctl.h
new file mode 100644
index 000000000000..ab0aba824a85
--- /dev/null
+++ b/include/uapi/linux/bcache-ioctl.h
@@ -0,0 +1,80 @@
+#ifndef _LINUX_BCACHE_IOCTL_H
+#define _LINUX_BCACHE_IOCTL_H
+
+#include <linux/bcache.h>
+#include <linux/uuid.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* global control dev: */
+
+#define BCH_FORCE_IF_DATA_MISSING (1 << 0)
+#define BCH_FORCE_IF_METADATA_MISSING (1 << 1)
+
+#define BCH_IOCTL_ASSEMBLE _IOW('r', 1, struct bch_ioctl_assemble)
+#define BCH_IOCTL_INCREMENTAL _IOW('r', 1, struct bch_ioctl_incremental)
+
+/* cache set control dev: */
+
+#define BCH_IOCTL_RUN _IO('r', 2)
+#define BCH_IOCTL_STOP _IO('r', 3)
+
+#define BCH_IOCTL_DISK_ADD _IOW('r', 4, struct bch_ioctl_disk_add)
+#define BCH_IOCTL_DISK_REMOVE _IOW('r', 5, struct bch_ioctl_disk_remove)
+#define BCH_IOCTL_DISK_FAIL _IOW('r', 6, struct bch_ioctl_disk_fail)
+
+#define BCH_IOCTL_DISK_REMOVE_BY_UUID \
+ _IOW('r', 5, struct bch_ioctl_disk_remove_by_uuid)
+#define BCH_IOCTL_DISK_FAIL_BY_UUID \
+ _IOW('r', 6, struct bch_ioctl_disk_fail_by_uuid)
+
+struct bch_ioctl_assemble {
+ __u32 flags;
+ __u32 nr_devs;
+ __u64 pad;
+ __u64 devs[];
+};
+
+struct bch_ioctl_incremental {
+ __u32 flags;
+ __u64 pad;
+ __u64 dev;
+};
+
+struct bch_ioctl_disk_add {
+ __u32 flags;
+ __u32 pad;
+ __u64 dev;
+};
+
+struct bch_ioctl_disk_remove {
+ __u32 flags;
+ __u32 pad;
+ __u64 dev;
+};
+
+struct bch_ioctl_disk_fail {
+ __u32 flags;
+ __u32 pad;
+ __u64 dev;
+};
+
+struct bch_ioctl_disk_remove_by_uuid {
+ __u32 flags;
+ __u32 pad;
+ uuid_le dev;
+};
+
+struct bch_ioctl_disk_fail_by_uuid {
+ __u32 flags;
+ __u32 pad;
+ uuid_le dev;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LINUX_BCACHE_IOCTL_H */