xtensa: ISS: add host file-based simulated disk
Victor Prupis [Mon, 19 May 2008 21:50:38 +0000 (14:50 -0700)]
Simdisk is a block device that maps to a file in the host file system.
It is usable for testing in the simulated environment, like xt-sim or
QEMU. Device binding to host file may be changed at runtime via proc
interface provided the device is not in use. Number of block devices
and initial binding to host files is controlled via kernel/module
parameters, with defaults specified in the kernel configuration.

Signed-off-by: Victor Prupis <vnp@tensilica.com>
Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
Signed-off-by: Chris Zankel <chris@zankel.net>

arch/xtensa/Kconfig
arch/xtensa/platforms/iss/Makefile
arch/xtensa/platforms/iss/simdisk.c [new file with mode: 0644]

index 79f0a2a..f83780f 100644 (file)
@@ -203,6 +203,42 @@ config BUILTIN_DTB
        string "DTB to build into the kernel image"
        depends on OF
 
+config BLK_DEV_SIMDISK
+       tristate "Host file-based simulated block device support"
+       default n
+       depends on XTENSA_PLATFORM_ISS
+       help
+         Create block devices that map to files in the host file system.
+         Device binding to host file may be changed at runtime via proc
+         interface provided the device is not in use.
+
+config BLK_DEV_SIMDISK_COUNT
+       int "Number of host file-based simulated block devices"
+       range 1 10
+       depends on BLK_DEV_SIMDISK
+       default 2
+       help
+         This is the default minimal number of created block devices.
+         Kernel/module parameter 'simdisk_count' may be used to change this
+         value at runtime. More file names (but no more than 10) may be
+         specified as parameters, simdisk_count grows accordingly.
+
+config SIMDISK0_FILENAME
+       string "Host filename for the first simulated device"
+       depends on BLK_DEV_SIMDISK = y
+       default ""
+       help
+         Attach a first simdisk to a host file. Conventionally, this file
+         contains a root file system.
+
+config SIMDISK1_FILENAME
+       string "Host filename for the second simulated device"
+       depends on BLK_DEV_SIMDISK = y && BLK_DEV_SIMDISK_COUNT != 1
+       default ""
+       help
+         Another simulated disk in a host file for a buildroot-independent
+         storage.
+
 source "mm/Kconfig"
 
 source "drivers/pcmcia/Kconfig"
index b7d1a5c..d2369b7 100644 (file)
@@ -6,3 +6,4 @@
 
 obj-y                  = console.o setup.o
 obj-$(CONFIG_NET)      += network.o
+obj-$(CONFIG_BLK_DEV_SIMDISK) += simdisk.o
diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c
new file mode 100644 (file)
index 0000000..f58ffc3
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * arch/xtensa/platforms/iss/simdisk.c
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2001-2013 Tensilica Inc.
+ *   Authors   Victor Prupis
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/proc_fs.h>
+#include <asm/uaccess.h>
+#include <platform/simcall.h>
+
+#define SIMDISK_MAJOR 240
+#define SECTOR_SHIFT 9
+#define SIMDISK_MINORS 1
+#define MAX_SIMDISK_COUNT 10
+
+struct simdisk {
+       const char *filename;
+       spinlock_t lock;
+       struct request_queue *queue;
+       struct gendisk *gd;
+       struct proc_dir_entry *procfile;
+       int users;
+       unsigned long size;
+       int fd;
+};
+
+
+static int simdisk_count = CONFIG_BLK_DEV_SIMDISK_COUNT;
+module_param(simdisk_count, int, S_IRUGO);
+MODULE_PARM_DESC(simdisk_count, "Number of simdisk units.");
+
+static int n_files;
+static const char *filename[MAX_SIMDISK_COUNT] = {
+#ifdef CONFIG_SIMDISK0_FILENAME
+       CONFIG_SIMDISK0_FILENAME,
+#ifdef CONFIG_SIMDISK1_FILENAME
+       CONFIG_SIMDISK1_FILENAME,
+#endif
+#endif
+};
+
+static int simdisk_param_set_filename(const char *val,
+               const struct kernel_param *kp)
+{
+       if (n_files < ARRAY_SIZE(filename))
+               filename[n_files++] = val;
+       else
+               return -EINVAL;
+       return 0;
+}
+
+static const struct kernel_param_ops simdisk_param_ops_filename = {
+       .set = simdisk_param_set_filename,
+};
+module_param_cb(filename, &simdisk_param_ops_filename, &n_files, 0);
+MODULE_PARM_DESC(filename, "Backing storage filename.");
+
+static int simdisk_major = SIMDISK_MAJOR;
+
+static void simdisk_transfer(struct simdisk *dev, unsigned long sector,
+               unsigned long nsect, char *buffer, int write)
+{
+       unsigned long offset = sector << SECTOR_SHIFT;
+       unsigned long nbytes = nsect << SECTOR_SHIFT;
+
+       if (offset > dev->size || dev->size - offset < nbytes) {
+               pr_notice("Beyond-end %s (%ld %ld)\n",
+                               write ? "write" : "read", offset, nbytes);
+               return;
+       }
+
+       spin_lock(&dev->lock);
+       while (nbytes > 0) {
+               unsigned long io;
+
+               __simc(SYS_lseek, dev->fd, offset, SEEK_SET, 0, 0);
+               if (write)
+                       io = simc_write(dev->fd, buffer, nbytes);
+               else
+                       io = simc_read(dev->fd, buffer, nbytes);
+               if (io == -1) {
+                       pr_err("SIMDISK: IO error %d\n", errno);
+                       break;
+               }
+               buffer += io;
+               offset += io;
+               nbytes -= io;
+       }
+       spin_unlock(&dev->lock);
+}
+
+static int simdisk_xfer_bio(struct simdisk *dev, struct bio *bio)
+{
+       int i;
+       struct bio_vec *bvec;
+       sector_t sector = bio->bi_sector;
+
+       bio_for_each_segment(bvec, bio, i) {
+               char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
+               unsigned len = bvec->bv_len >> SECTOR_SHIFT;
+
+               simdisk_transfer(dev, sector, len, buffer,
+                               bio_data_dir(bio) == WRITE);
+               sector += len;
+               __bio_kunmap_atomic(bio, KM_USER0);
+       }
+       return 0;
+}
+
+static void simdisk_make_request(struct request_queue *q, struct bio *bio)
+{
+       struct simdisk *dev = q->queuedata;
+       int status = simdisk_xfer_bio(dev, bio);
+       bio_endio(bio, status);
+}
+
+
+static int simdisk_open(struct block_device *bdev, fmode_t mode)
+{
+       struct simdisk *dev = bdev->bd_disk->private_data;
+
+       spin_lock(&dev->lock);
+       if (!dev->users)
+               check_disk_change(bdev);
+       ++dev->users;
+       spin_unlock(&dev->lock);
+       return 0;
+}
+
+static int simdisk_release(struct gendisk *disk, fmode_t mode)
+{
+       struct simdisk *dev = disk->private_data;
+       spin_lock(&dev->lock);
+       --dev->users;
+       spin_unlock(&dev->lock);
+       return 0;
+}
+
+static const struct block_device_operations simdisk_ops = {
+       .owner          = THIS_MODULE,
+       .open           = simdisk_open,
+       .release        = simdisk_release,
+};
+
+static struct simdisk *sddev;
+static struct proc_dir_entry *simdisk_procdir;
+
+static int simdisk_attach(struct simdisk *dev, const char *filename)
+{
+       int err = 0;
+
+       filename = kstrdup(filename, GFP_KERNEL);
+       if (filename == NULL)
+               return -ENOMEM;
+
+       spin_lock(&dev->lock);
+
+       if (dev->fd != -1) {
+               err = -EBUSY;
+               goto out;
+       }
+       dev->fd = simc_open(filename, O_RDWR, 0);
+       if (dev->fd == -1) {
+               pr_err("SIMDISK: Can't open %s: %d\n", filename, errno);
+               err = -ENODEV;
+               goto out;
+       }
+       dev->size = __simc(SYS_lseek, dev->fd, 0, SEEK_END, 0, 0);
+       set_capacity(dev->gd, dev->size >> SECTOR_SHIFT);
+       dev->filename = filename;
+       pr_info("SIMDISK: %s=%s\n", dev->gd->disk_name, dev->filename);
+out:
+       if (err)
+               kfree(filename);
+       spin_unlock(&dev->lock);
+
+       return err;
+}
+
+static int simdisk_detach(struct simdisk *dev)
+{
+       int err = 0;
+
+       spin_lock(&dev->lock);
+
+       if (dev->users != 0) {
+               err = -EBUSY;
+       } else if (dev->fd != -1) {
+               if (simc_close(dev->fd)) {
+                       pr_err("SIMDISK: error closing %s: %d\n",
+                                       dev->filename, errno);
+                       err = -EIO;
+               } else {
+                       pr_info("SIMDISK: %s detached from %s\n",
+                                       dev->gd->disk_name, dev->filename);
+                       dev->fd = -1;
+                       kfree(dev->filename);
+                       dev->filename = NULL;
+               }
+       }
+       spin_unlock(&dev->lock);
+       return err;
+}
+
+static int proc_read_simdisk(char *page, char **start, off_t off,
+               int count, int *eof, void *data)
+{
+       int len;
+       struct simdisk *dev = (struct simdisk *) data;
+       len = sprintf(page, "%s\n", dev->filename ? dev->filename : "");
+       return len;
+}
+
+static int proc_write_simdisk(struct file *file, const char *buffer,
+               unsigned long count, void *data)
+{
+       char *tmp = kmalloc(count + 1, GFP_KERNEL);
+       struct simdisk *dev = (struct simdisk *) data;
+       int err;
+
+       if (tmp == NULL)
+               return -ENOMEM;
+       if (copy_from_user(tmp, buffer, count)) {
+               err = -EFAULT;
+               goto out_free;
+       }
+
+       err = simdisk_detach(dev);
+       if (err != 0)
+               goto out_free;
+
+       if (count > 0 && tmp[count - 1] == '\n')
+               tmp[count - 1] = 0;
+       else
+               tmp[count] = 0;
+
+       if (tmp[0])
+               err = simdisk_attach(dev, tmp);
+
+       if (err == 0)
+               err = count;
+out_free:
+       kfree(tmp);
+       return err;
+}
+
+static int __init simdisk_setup(struct simdisk *dev, int which,
+               struct proc_dir_entry *procdir)
+{
+       char tmp[2] = { '0' + which, 0 };
+
+       dev->fd = -1;
+       dev->filename = NULL;
+       spin_lock_init(&dev->lock);
+       dev->users = 0;
+
+       dev->queue = blk_alloc_queue(GFP_KERNEL);
+       if (dev->queue == NULL) {
+               pr_err("blk_alloc_queue failed\n");
+               goto out_alloc_queue;
+       }
+
+       blk_queue_make_request(dev->queue, simdisk_make_request);
+       dev->queue->queuedata = dev;
+
+       dev->gd = alloc_disk(SIMDISK_MINORS);
+       if (dev->gd == NULL) {
+               pr_err("alloc_disk failed\n");
+               goto out_alloc_disk;
+       }
+       dev->gd->major = simdisk_major;
+       dev->gd->first_minor = which;
+       dev->gd->fops = &simdisk_ops;
+       dev->gd->queue = dev->queue;
+       dev->gd->private_data = dev;
+       snprintf(dev->gd->disk_name, 32, "simdisk%d", which);
+       set_capacity(dev->gd, 0);
+       add_disk(dev->gd);
+
+       dev->procfile = create_proc_entry(tmp, 0644, procdir);
+       dev->procfile->data = dev;
+       dev->procfile->read_proc = proc_read_simdisk;
+       dev->procfile->write_proc = proc_write_simdisk;
+       return 0;
+
+out_alloc_disk:
+       blk_cleanup_queue(dev->queue);
+       dev->queue = NULL;
+out_alloc_queue:
+       simc_close(dev->fd);
+       return -EIO;
+}
+
+static int __init simdisk_init(void)
+{
+       int i;
+
+       if (register_blkdev(simdisk_major, "simdisk") < 0) {
+               pr_err("SIMDISK: register_blkdev: %d\n", simdisk_major);
+               return -EIO;
+       }
+       pr_info("SIMDISK: major: %d\n", simdisk_major);
+
+       if (n_files > simdisk_count)
+               simdisk_count = n_files;
+       if (simdisk_count > MAX_SIMDISK_COUNT)
+               simdisk_count = MAX_SIMDISK_COUNT;
+
+       sddev = kmalloc(simdisk_count * sizeof(struct simdisk),
+                       GFP_KERNEL);
+       if (sddev == NULL)
+               goto out_unregister;
+
+       simdisk_procdir = proc_mkdir("simdisk", 0);
+       if (simdisk_procdir == NULL)
+               goto out_free_unregister;
+
+       for (i = 0; i < simdisk_count; ++i) {
+               if (simdisk_setup(sddev + i, i, simdisk_procdir) == 0) {
+                       if (filename[i] != NULL && filename[i][0] != 0 &&
+                                       (n_files == 0 || i < n_files))
+                               simdisk_attach(sddev + i, filename[i]);
+               }
+       }
+
+       return 0;
+
+out_free_unregister:
+       kfree(sddev);
+out_unregister:
+       unregister_blkdev(simdisk_major, "simdisk");
+       return -ENOMEM;
+}
+module_init(simdisk_init);
+
+static void simdisk_teardown(struct simdisk *dev, int which,
+               struct proc_dir_entry *procdir)
+{
+       char tmp[2] = { '0' + which, 0 };
+
+       simdisk_detach(dev);
+       if (dev->gd)
+               del_gendisk(dev->gd);
+       if (dev->queue)
+               blk_cleanup_queue(dev->queue);
+       remove_proc_entry(tmp, procdir);
+}
+
+static void __exit simdisk_exit(void)
+{
+       int i;
+
+       for (i = 0; i < simdisk_count; ++i)
+               simdisk_teardown(sddev + i, i, simdisk_procdir);
+       remove_proc_entry("simdisk", 0);
+       kfree(sddev);
+       unregister_blkdev(simdisk_major, "simdisk");
+}
+module_exit(simdisk_exit);
+
+MODULE_ALIAS_BLOCKDEV_MAJOR(SIMDISK_MAJOR);
+
+MODULE_LICENSE("GPL");