From patchwork Mon Sep 26 12:38:00 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: David Wagner X-Patchwork-Id: 116415 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from merlin.infradead.org (merlin.infradead.org [IPv6:2001:4978:20e::2]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id C413AB6F68 for ; Mon, 26 Sep 2011 22:38:01 +1000 (EST) Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1R8ARE-0006ZF-UU; Mon, 26 Sep 2011 12:37:37 +0000 Received: from localhost ([127.0.0.1] helo=canuck.infradead.org) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1R8ARE-0008Jz-Gj; Mon, 26 Sep 2011 12:37:36 +0000 Received: from mail.free-electrons.com ([88.190.12.23]) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1R8AR8-0008JT-Qr for linux-mtd@lists.infradead.org; Mon, 26 Sep 2011 12:37:34 +0000 Received: by mail.free-electrons.com (Postfix, from userid 106) id B08F1192; Mon, 26 Sep 2011 14:37:26 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.free-electrons.com X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=ALL_TRUSTED,BAYES_00 autolearn=ham version=3.3.1 Received: from localhost.localdomain (col31-4-88-188-83-94.fbx.proxad.net [88.188.83.94]) by mail.free-electrons.com (Postfix) with ESMTPSA id D01E5136; Mon, 26 Sep 2011 14:37:13 +0200 (CEST) From: David Wagner To: linux-mtd Subject: [PATCHv7] UBI: new module ubiblk: block layer on top of UBI Date: Mon, 26 Sep 2011 14:38:00 +0200 Message-Id: <1317040680-6199-1-git-send-email-david.wagner@free-electrons.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: <1308922482-14967-1-git-send-email-david.wagner@free-electrons.com> References: <1308922482-14967-1-git-send-email-david.wagner@free-electrons.com> X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.7.6 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110926_083731_471501_6437A1B3 X-CRM114-Status: GOOD ( 43.88 ) X-Spam-Score: -0.5 (/) X-Spam-Report: SpamAssassin version 3.3.1 on canuck.infradead.org summary: Content analysis details: (-0.5 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.5 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain Cc: Ricard Wanderlof , Arnd Bergmann , linux-embedded , Artem Bityutskiy , David Wagner , lkml , Tim Bird , David Woodhouse X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-mtd-bounces@lists.infradead.org Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org ubiblk is a read-only block layer on top of UBI. It presents UBI volumes as read-only block devices (named ubiblkX_Y, where X is the UBI device number and Y the Volume ID). It is used by putting a block filesystem image on a UBI volume, creating the corresponding ubiblk device and then mounting it. It uses the UBI API to register to UBI notifications and to read from the volumes. It also creates a ubiblk_ctrl device node that simply receives ioctl from a userspace tool for creating/removing ubiblk devices. Some code is taken from mtd_blkdevs and gluebi. Some code for the ioctl part is also inspired from ubi's core. Advantages of ubiblk over gluebi+mtdblock_ro: * Simpler architecture * The numbering of devices is much easier with ubiblk than with gluebi+mtdblock_ro. With gluebi+mtdblock_ro, you get one additional MTD device for each UBI volume, so the number of MTD devices grows quite a lot and is a bit difficult to understand. For example, mtdblock[0-4] might be your real MTD partitions, while mtdblock[5-9] might be your UBI volumes. It also means that if a new real MTD partition is added, the index of all the MTD devices exposing UBI volumes will be shifted by one, which is a bit confusing/annoying. As well, if you add an UBI volume, the mtdblock devices that are emulated on top of volumes that come after this new one will have their ID incremented. * ubiblk devices are created on a 'on-demand' basis, which avoids wasting resources. * It is also possible to specify a "volume" parameter in order to create a ubiblk device at init time. This makes possible to put a rootfs on a ubiblk device. Format: ":" * The performance appears to be slightly better with ubiblk than gluebi+mtdblock_ro, according to our benchmarks (see http://elinux.org/Flash_Filesystem_Benchmarks_2.6.39) Signed-off-by: David Wagner Cc: Artem Bityutskiy Cc: Arnd Bergmann --- changes since v6: ~~~~~~~~~~~~~~~~~ For convenience the v6/v7 diff can be seen at http://code.bulix.org/isds0w-80618?raw * "volume" parameter: pass the volume name instead of the volume ID * Change the initialization order: now, the inittime volume is created at the very end of the init: it avoids creating an error path. * Add error management in the parsing * Homogenize the name of the error path labels * Remove the "ubiblk_" prefix of some functions and rename some * Correct the format of the kerneldoc comments and homogenize with the rest of UBI * Remove useless initializations and comments @Ricard Wanderlof: Thanks for testing and for your feedback. I just saw that you were missing the ability to use the volume name instead of the volume ID ; that also was a request from Artem, so here it is. Thanks for spotting the typo as well. As for the userland tools, there is a separate patch against linux-mtd. The latest version isn't in any mailing-list yet but I pasted it for you at http://code.bulix.org/4iz8u3-80619?raw Documentation/ioctl/ioctl-number.txt | 1 + drivers/mtd/ubi/Kconfig | 16 + drivers/mtd/ubi/Makefile | 1 + drivers/mtd/ubi/ubiblk.c | 748 ++++++++++++++++++++++++++++++++++ include/mtd/Kbuild | 1 + include/mtd/ubiblk-user.h | 48 +++ 6 files changed, 815 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/ubi/ubiblk.c create mode 100644 include/mtd/ubiblk-user.h diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 845a191..b24df7f 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -150,6 +150,7 @@ Code Seq#(hex) Include File Comments 'M' 00-0F drivers/video/fsl-diu-fb.h conflict! 'N' 00-1F drivers/usb/scanner.h 'O' 00-06 mtd/ubi-user.h UBI +'O' 10-11 mtd/ubiblk-user.h ubiblk 'P' all linux/soundcard.h conflict! 'P' 60-6F sound/sscape_ioctl.h conflict! 'P' 00-0F drivers/usb/class/usblp.c conflict! diff --git a/drivers/mtd/ubi/Kconfig b/drivers/mtd/ubi/Kconfig index 4dcc752..977934a 100644 --- a/drivers/mtd/ubi/Kconfig +++ b/drivers/mtd/ubi/Kconfig @@ -60,4 +60,20 @@ config MTD_UBI_DEBUG help This option enables UBI debugging. +config MTD_UBI_UBIBLK + tristate "Read-only block transition layer on top of UBI" + help + Read-only block interface on top of UBI. + + This option adds ubiblk, which creates a read-ony block device from + UBI volumes. It makes it possible to use R/O block filesystems on + top of UBI volumes (and hence, on top of MTDs while avoiding bad + blocks). + + ubiblk devices are created by invoking appropriate ioctl to the + ubiblk_ctrl device node. + + The devices are named ubiblkX_Y where X is the UBI number and Y is + the Volume ID. + endif # MTD_UBI diff --git a/drivers/mtd/ubi/Makefile b/drivers/mtd/ubi/Makefile index c9302a5..354b2df 100644 --- a/drivers/mtd/ubi/Makefile +++ b/drivers/mtd/ubi/Makefile @@ -5,3 +5,4 @@ ubi-y += misc.o ubi-$(CONFIG_MTD_UBI_DEBUG) += debug.o obj-$(CONFIG_MTD_UBI_GLUEBI) += gluebi.o +obj-$(CONFIG_MTD_UBI_UBIBLK) += ubiblk.o diff --git a/drivers/mtd/ubi/ubiblk.c b/drivers/mtd/ubi/ubiblk.c new file mode 100644 index 0000000..41f47b2 --- /dev/null +++ b/drivers/mtd/ubi/ubiblk.c @@ -0,0 +1,748 @@ +/* + * Copyright (c) Free Electrons, 2011 + * Copyright (c) International Business Machines Corp., 2006 + * Copyright © 2003-2010 David Woodhouse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David Wagner + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ubi.h" +#include "ubi-media.h" + +#define BLK_SIZE 512 + +/** + * struct ubiblk_dev - represents a ubiblk device, proxying a UBI volume + * @desc: open UBI volume descriptor + * @vi: UBI volume information + * @ubi_num: UBI device number + * @vol_id: UBI volume identifier + * @refcnt: reference counter (increases with open(), decreases with release()) + * @gd: the disk (block device) created by ubiblk + * @rq: the request queue to @gd + * @req_task: the thread processing @rq requests + * @vol_lock: protects write access to the elements of this structure + * @queue_lock: avoids concurrent accesses to the request queue + * @list: linked list structure + */ +struct ubiblk_dev { + struct ubi_volume_desc *desc; + struct ubi_volume_info *vi; + int ubi_num; + int vol_id; + int refcnt; + + struct gendisk *gd; + struct request_queue *rq; + struct task_struct *req_task; + + struct mutex vol_lock; + + spinlock_t queue_lock; + + struct list_head list; +}; + +/* Linked list of all ubiblk_dev instances */ +static LIST_HEAD(ubiblk_devs); + +/* Avoid concurrent access to the above list */ +static DEFINE_MUTEX(devlist_lock); + +static int ubiblk_major; +static const struct block_device_operations ubiblk_ops; + +/* The device receiving the ioctls */ +static struct miscdevice ctrl_dev; + +/* +3 is for the separator and the UBI device num */ +#define VOL_PARAM_MAXLEN (UBI_VOL_NAME_MAX + 3) +static char *volume; +module_param(volume, charp, 0000); +MODULE_PARM_DESC(volume, + "Format: volume=:\n" + "Create a ubiblk device at init time. Useful for mounting it as root " + "device."); + +static struct ubiblk_dev *find_dev(struct ubi_volume_info *vi) +{ + struct ubiblk_dev *dev; + + list_for_each_entry(dev, &ubiblk_devs, list) { + if (dev && dev->ubi_num == vi->ubi_num && + dev->vol_id == vi->vol_id) + return dev; + } + return NULL; +} + +/** + * do_ubiblk_request - Read a LEB and fill the request buffer with the + * requested sector. + * @req: the request data structure + * @dev: the ubiblk device on which the request is issued + */ +static int do_ubiblk_request(struct request *req, struct ubiblk_dev *dev) +{ + unsigned long start, len, read_bytes; + int offset; + int leb; + int ret; + + start = blk_rq_pos(req) << 9; + len = blk_rq_cur_bytes(req); + read_bytes = 0; + + /* We are always reading. No need to handle writing for now */ + + leb = start / dev->vi->usable_leb_size; + offset = start % dev->vi->usable_leb_size; + + do { + if (offset + len > dev->vi->usable_leb_size) + len = dev->vi->usable_leb_size - offset; + + if (unlikely(blk_rq_pos(req) + blk_rq_cur_sectors(req) > + get_capacity(req->rq_disk))) { + dev_err(disk_to_dev(dev->gd), + "attempting to read too far\n"); + return -EIO; + } + + /* Read (len) bytes of LEB (leb) from (offset) and put the + * result in the buffer given by the request. + * If the request is overlapping on several lebs, (read_bytes) + * will be > 0 and the data will be put in the buffer at + * offset (read_bytes) + */ + ret = ubi_read(dev->desc, leb, req->buffer + read_bytes, + offset, len); + + if (ret) { + dev_err(disk_to_dev(dev->gd), "ubi_read error\n"); + return ret; + } + + read_bytes += len; + + len = blk_rq_cur_bytes(req) - read_bytes; + leb++; + offset = 0; + } while (read_bytes < blk_rq_cur_bytes(req)); + + return 0; +} + +/** + * ubiblk_request - wakes the processing thread + * @rq: the request queue which device is to be awaken + */ +static void ubiblk_request(struct request_queue *rq) +{ + struct ubiblk_dev *dev; + struct request *req; + + dev = rq->queuedata; + + if (!dev) + while ((req = blk_fetch_request(rq)) != NULL) + __blk_end_request_all(req, -ENODEV); + else + wake_up_process(dev->req_task); +} + +/** + * ubiblk_open - open a UBI volume (get the volume descriptor). + * @bdev: the corresponding block device + * @mode: opening mode (don't care as long as ubiblk is read-only) + */ +static int ubiblk_open(struct block_device *bdev, fmode_t mode) +{ + struct ubiblk_dev *dev = bdev->bd_disk->private_data; + int err; + + mutex_lock(&dev->vol_lock); + dev_dbg(disk_to_dev(dev->gd), "open(); refcnt = %d\n", dev->refcnt); + if (dev->refcnt > 0) { + /* + * The volume is already opened ; just increase the reference + * counter. + */ + dev->refcnt++; + mutex_unlock(&dev->vol_lock); + return 0; + } + + dev->desc = ubi_open_volume(dev->ubi_num, dev->vol_id, + UBI_READONLY); + if (IS_ERR(dev->desc)) { + dev_err(disk_to_dev(dev->gd), "failed to open"); + + err = PTR_ERR(dev->desc); + dev->desc = NULL; + goto out_unlock; + } + + dev->vi = kzalloc(sizeof(struct ubi_volume_info), GFP_KERNEL); + if (!dev->vi) { + err = -ENOMEM; + goto out_close; + } + ubi_get_volume_info(dev->desc, dev->vi); + + dev->refcnt++; + dev_dbg(disk_to_dev(dev->gd), "opened mode=%d\n", mode); + mutex_unlock(&dev->vol_lock); + return 0; + +out_close: + ubi_close_volume(dev->desc); + dev->desc = NULL; +out_unlock: + mutex_unlock(&dev->vol_lock); + return err; +} + +/** + * ubiblk_release - close a UBI volume (close the volume descriptor). + * @gd: the disk that was previously opened + * @mode: don't care + */ +static int ubiblk_release(struct gendisk *gd, fmode_t mode) +{ + struct ubiblk_dev *dev = gd->private_data; + + mutex_lock(&dev->vol_lock); + dev_dbg(disk_to_dev(dev->gd), "release(); refcnt = %d\n", dev->refcnt); + + dev->refcnt--; + if (dev->refcnt == 0) { + kfree(dev->vi); + dev->vi = NULL; + + ubi_close_volume(dev->desc); + dev->desc = NULL; + + dev_dbg(disk_to_dev(dev->gd), "released, mode=%d\n", mode); + } + + mutex_unlock(&dev->vol_lock); + return 0; +} + +/** + * ubiblk_thread - loop on the block request queue and wait for new + * requests ; run them with do_ubiblk_request(). Mostly copied from + * mtd_blkdevs.c. + * @arg: the ubiblk device which request queue to process + */ +static int ubiblk_thread(void *arg) +{ + struct ubiblk_dev *dev = arg; + struct request_queue *rq = dev->rq; + struct request *req = NULL; + + spin_lock_irq(rq->queue_lock); + + while (!kthread_should_stop()) { + int res; + + if (!req) + req = blk_fetch_request(rq); + if (!req) { + set_current_state(TASK_INTERRUPTIBLE); + + if (kthread_should_stop()) + set_current_state(TASK_RUNNING); + + spin_unlock_irq(rq->queue_lock); + schedule(); + spin_lock_irq(rq->queue_lock); + continue; + } + + spin_unlock_irq(rq->queue_lock); + + mutex_lock(&dev->vol_lock); + res = do_ubiblk_request(req, dev); + mutex_unlock(&dev->vol_lock); + + spin_lock_irq(rq->queue_lock); + + if (!__blk_end_request_cur(req, res)) + req = NULL; + } + + if (req) + __blk_end_request_all(req, -EIO); + + spin_unlock_irq(rq->queue_lock); + + return 0; +} + +/** + * ubiblk_create - create a ubiblk device proxying a UBI volume. + * @vi: the UBI volume information data structure + * + * An UBI volume has been created ; create a corresponding ubiblk device: + * Initialize the locks, the structure, the block layer infos and start a + * req_task. + */ +static int ubiblk_create(struct ubi_volume_info *vi) +{ + struct ubiblk_dev *dev; + struct gendisk *gd; + int disk_capacity; + int ret; + + mutex_lock(&devlist_lock); + /* Check that the volume isn't already proxyfied */ + if (find_dev(vi)) { + ret = -EEXIST; + goto out_unlock; + } + + dev = kzalloc(sizeof(struct ubiblk_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto out_unlock; + } + + mutex_init(&dev->vol_lock); + + dev->ubi_num = vi->ubi_num; + dev->vol_id = vi->vol_id; + + /* Initialize the gendisk of this ubiblk device */ + gd = alloc_disk(1); + if (!gd) { + pr_err("alloc_disk failed\n"); + ret = -ENODEV; + goto out_free_dev; + } + + gd->fops = &ubiblk_ops; + gd->major = ubiblk_major; + gd->first_minor = dev->ubi_num * UBI_MAX_VOLUMES + dev->vol_id; + gd->private_data = dev; + sprintf(gd->disk_name, "ubiblk%d_%d", dev->ubi_num, dev->vol_id); + disk_capacity = (vi->size * vi->usable_leb_size) >> 9; + set_capacity(gd, disk_capacity); + set_disk_ro(gd, 1); + dev->gd = gd; + + spin_lock_init(&dev->queue_lock); + dev->rq = blk_init_queue(ubiblk_request, &dev->queue_lock); + if (!dev->rq) { + pr_err("blk_init_queue failed\n"); + ret = -ENODEV; + goto out_put_disk; + } + dev->rq->queuedata = dev; + blk_queue_logical_block_size(dev->rq, BLK_SIZE); + dev->gd->queue = dev->rq; + + /* Borrowed from mtd_blkdevs.c */ + /* Create processing req_task + * + * The processing of the request has to be done in process context (it + * might sleep) but blk_run_queue can't block ; so we need to separate + * the event of a request being added to the queue (which triggers the + * callback ubiblk_request - that is set with blk_init_queue()) + * and the processing of that request. + * + * Thus, the sole purpose of ubi_ubiblk_reuqest is to wake the kthread + * up so that it will process the request queue + */ + dev->req_task = kthread_run(ubiblk_thread, dev, "%s%d_%d", + "kubiblk", dev->ubi_num, dev->vol_id); + if (IS_ERR(dev->req_task)) { + ret = PTR_ERR(dev->req_task); + goto out_cleanup_queue; + } + + list_add(&dev->list, &ubiblk_devs); + add_disk(dev->gd); + + dev_info(disk_to_dev(dev->gd), + "created from ubi%d:%d(%s)\n", dev->ubi_num, dev->vol_id, + vi->name); + + mutex_unlock(&devlist_lock); + + return 0; + +out_cleanup_queue: + blk_cleanup_queue(dev->rq); +out_put_disk: + put_disk(dev->gd); +out_free_dev: + kfree(dev); +out_unlock: + mutex_unlock(&devlist_lock); + + return ret; +} + +/** + * ubiblk_remove - destroy a ubiblk device. + * @vi: the UBI volume information data structure + * + * A UBI volume has been removed or we are requested to unproxify a volume ; + * destroy the corresponding ubiblk device. + */ +static int ubiblk_remove(struct ubi_volume_info *vi) +{ + struct ubiblk_dev *dev; + + mutex_lock(&devlist_lock); + + dev = find_dev(vi); + if (!dev) { + mutex_unlock(&devlist_lock); + pr_warn("trying to remove %s, but it isn't handled\n", + vi->name); + return -ENODEV; + } + + mutex_lock(&dev->vol_lock); + if (dev->desc) { + mutex_unlock(&dev->vol_lock); + mutex_unlock(&devlist_lock); + return -EBUSY; + } + + del_gendisk(dev->gd); + blk_cleanup_queue(dev->rq); + kthread_stop(dev->req_task); + put_disk(dev->gd); + + list_del(&dev->list); + mutex_unlock(&dev->vol_lock); + mutex_unlock(&devlist_lock); + + kfree(dev); + pr_info("unproxyfied %s\n", vi->name); + return 0; +} + +/** + * ubiblk_resize - resize a ubiblk device. + * @vi: the UBI volume information data structure + * + * A UBI volume has been resized, change the ubiblk device geometry accordingly. + */ +static int ubiblk_resize(struct ubi_volume_info *vi) +{ + struct ubiblk_dev *dev; + int disk_capacity; + + /* We don't touch the list, but we better lock it: it could be that the + * device gets removed between the time the device has been found and + * the time we access dev->gd + */ + mutex_lock(&devlist_lock); + dev = find_dev(vi); + if (!dev) { + mutex_unlock(&devlist_lock); + pr_warn("trying to resize %s, which isn't handled\n", + vi->name); + return -ENODEV; + } + + mutex_lock(&dev->vol_lock); + disk_capacity = (vi->size * vi->usable_leb_size) >> 9; + set_capacity(dev->gd, disk_capacity); + dev_dbg(disk_to_dev(dev->gd), "resized to %d LEBs\n", vi->size); + mutex_unlock(&dev->vol_lock); + + mutex_unlock(&devlist_lock); + return 0; +} + +/** + * ubiblk_notify - dispatches the UBI notifications. + * @nb: unused + * @notification_type: the notification type sent by UBI + * @ns_ptr: contains the notifications' additional informations + */ +static int ubiblk_notify(struct notifier_block *nb, + unsigned long notification_type, void *ns_ptr) +{ + struct ubi_notification *nt = ns_ptr; + + switch (notification_type) { + case UBI_VOLUME_REMOVED: + ubiblk_remove(&nt->vi); + break; + case UBI_VOLUME_RESIZED: + ubiblk_resize(&nt->vi); + break; + default: + break; + } + return NOTIFY_OK; +} + +static const struct block_device_operations ubiblk_ops = { + .owner = THIS_MODULE, + .open = ubiblk_open, + .release = ubiblk_release, +}; + +static struct notifier_block ubiblk_notifier = { + .notifier_call = ubiblk_notify, +}; + + +/** + * ubiblk_ctrl_ioctl - ioctl handling for proxying/unproxying a UBI volume. + * @file: the file on which the ioctl was invoked (unused) + * @cmd: the ioctl type + * @arg: additional command informations + */ +static long ubiblk_ctrl_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int err; + void __user *argp = (void __user *)arg; + + struct ubi_volume_desc *desc; + struct ubi_volume_info vi; + struct ubiblk_ctrl_req req; + + if (!capable(CAP_SYS_RESOURCE)) + return -EPERM; + + err = copy_from_user(&req, argp, sizeof(struct ubiblk_ctrl_req)); + if (err) + return -EFAULT; + + if (req.ubi_num < 0 || req.vol_id < 0) + return -EINVAL; + + desc = ubi_open_volume(req.ubi_num, req.vol_id, UBI_READONLY); + if (IS_ERR(desc)) { + dev_err(ctrl_dev.this_device, "opening ubi%d:%d failed\n", + req.ubi_num, req.vol_id); + return PTR_ERR(desc); + } + + ubi_get_volume_info(desc, &vi); + + switch (cmd) { + case UBIBLK_IOCADD: + dev_info(ctrl_dev.this_device, "proxying ubi%d:%d\n", + req.ubi_num, req.vol_id); + err = ubiblk_create(&vi); + break; + case UBIBLK_IOCDEL: + dev_info(ctrl_dev.this_device, "unproxying ubi%d:%d\n", + req.ubi_num, req.vol_id); + err = ubiblk_remove(&vi); + break; + + default: + err = -ENOTTY; + break; + } + + ubi_close_volume(desc); + + return err; +} + +/* ubiblk control device (receives ioctls) */ +static const struct file_operations ubiblk_ctrl_ops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ubiblk_ctrl_ioctl, + .llseek = no_llseek, +}; +static struct miscdevice ctrl_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ubiblk_ctrl", + .fops = &ubiblk_ctrl_ops, +}; + +/** + * volume_param_parse - parse the "volume" module parameter. + * @ubi_num: where to store the UBI device number + * @vol_name: where to store the volume name (fixed lenght, at least + * UBI_VOL_NAME_MAX) + */ +static int __init volume_param_parse(int *ubi_num, char *vol_name) +{ + char *tokens[2] = {NULL, NULL}; + char buf[VOL_PARAM_MAXLEN + 1]; + char *pbuf = buf; + int len = strlen(volume); + int err; + + if (len > VOL_PARAM_MAXLEN || len == 0) + return -EINVAL; + + strcpy(buf, volume); + tokens[0] = strsep(&pbuf, ":"); + tokens[1] = strsep(&pbuf, ":"); + + if (pbuf) + return -EINVAL; /* There are surnumerous parameters */ + + err = kstrtoint(tokens[0], 10, ubi_num); + if (err < 0 || *ubi_num < 0) + return -EINVAL; + + len = strlen(tokens[1]); + if (len > UBI_VOL_NAME_MAX || len == 0) + return -EINVAL; + strcpy(vol_name, tokens[1]); + + return 0; +} + +/** + * inittime_volume - create a volume at init time. + * + * If the user passed a "ubiblk.volume" argument, check it and create the said + * volume. + */ +static int __init inittime_volume(void) +{ + int ubi_num; + char vol_name[UBI_VOL_NAME_MAX + 1]; + int err; + struct ubi_volume_desc *desc; + struct ubi_volume_info vi; + + err = volume_param_parse(&ubi_num, vol_name); + if (err < 0) { + pr_err("cannot parse the volume parameter"); + return err; + } + + desc = ubi_open_volume_nm(ubi_num, vol_name, UBI_READONLY); + if (IS_ERR(desc)) { + pr_err("opening ubi%d:%s failed: %ld\n", ubi_num, vol_name, + PTR_ERR(desc)); + return PTR_ERR(desc); + } + ubi_get_volume_info(desc, &vi); + + err = ubiblk_create(&vi); + if (err < 0) + pr_err("can't create the initial device " + "ubiblk%d_%d: %d\n", ubi_num, vi.vol_id, err); + ubi_close_volume(desc); + + return err; +} + +/** + * ubiblk_init - initialize the module. + * + * Get a major number and register to UBI notifications ; register the ioctl + * handler device. + */ +static int __init ubiblk_init(void) +{ + int ret; + + ret = register_blkdev(0, "ubiblk"); + if (ret < 0) + return ret; + ubiblk_major = ret; + + ret = ubi_register_volume_notifier(&ubiblk_notifier, 1); + if (ret < 0) + goto out_unreg_blk; + + ret = misc_register(&ctrl_dev); + if (ret < 0) { + pr_err("can't register control device\n"); + goto out_unreg_notifier; + } + + /* Check if the user wants a volume to be proxified at init time */ + if (volume) { + ret = inittime_volume(); + if (ret < 0) + goto out_unreg_misc; + } + + pr_info("major device number is %d\n", ubiblk_major); + + return ret; + +out_unreg_misc: + misc_deregister(&ctrl_dev); +out_unreg_notifier: + ubi_unregister_volume_notifier(&ubiblk_notifier); +out_unreg_blk: + unregister_blkdev(ubiblk_major, "ubiblk"); + + return ret; +} + +/** + * ubiblk_exit - end of life. + * + * Unregister the block device major, unregister from UBI notifications, + * unregister the ioctl handler device, stop the threads and free the memory. + */ +static void __exit ubiblk_exit(void) +{ + struct ubiblk_dev *next; + struct ubiblk_dev *dev; + + ubi_unregister_volume_notifier(&ubiblk_notifier); + misc_deregister(&ctrl_dev); + + list_for_each_entry_safe(dev, next, &ubiblk_devs, list) { + /* The module is being forcefully removed */ + WARN_ON(dev->desc); + + del_gendisk(dev->gd); + blk_cleanup_queue(dev->rq); + kthread_stop(dev->req_task); + put_disk(dev->gd); + + kfree(dev); + } + + unregister_blkdev(ubiblk_major, "ubiblk"); +} + +module_init(ubiblk_init); +module_exit(ubiblk_exit); +MODULE_DESCRIPTION("Read-only block transition layer on top of UBI"); +MODULE_AUTHOR("David Wagner"); +MODULE_LICENSE("GPL"); diff --git a/include/mtd/Kbuild b/include/mtd/Kbuild index 192f8fb..d0d59d8 100644 --- a/include/mtd/Kbuild +++ b/include/mtd/Kbuild @@ -3,3 +3,4 @@ header-y += mtd-abi.h header-y += mtd-user.h header-y += nftl-user.h header-y += ubi-user.h +header-y += ubiblk-user.h diff --git a/include/mtd/ubiblk-user.h b/include/mtd/ubiblk-user.h new file mode 100644 index 0000000..61692d5 --- /dev/null +++ b/include/mtd/ubiblk-user.h @@ -0,0 +1,48 @@ +/* + * Copyright © Free Electrons, 2011 + * Copyright © International Business Machines Corp., 2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David Wagner + */ + +#ifndef __UBIBLK_USER_H__ +#define __UBIBLK_USER_H__ + +#include + +/** + * ubiblk_ctrl_req - additional ioctl data structure + * @ubi_num: UBI device number + * @vol_id: UBI volume identifier + * @padding: reserved for future, must contain zeroes + */ +struct ubiblk_ctrl_req { + __s32 ubi_num; + __s32 vol_id; + __u8 padding[8]; +} __packed; + +/* ioctl commands of the UBI control character device */ +#define UBIBLK_CTRL_IOC_MAGIC 'O' + +/* Create a ubiblk device from a UBI volume */ +#define UBIBLK_IOCADD _IOW(UBIBLK_CTRL_IOC_MAGIC, 0x10, struct ubiblk_ctrl_req) +/* Delete a ubiblk device */ +#define UBIBLK_IOCDEL _IOW(UBIBLK_CTRL_IOC_MAGIC, 0x11, struct ubiblk_ctrl_req) +/* If you add ioctls here, please note that UBI uses 'O'/0x00-0x06 */ + +#endif