NOMMU: Present backing device capabilities for MTD chardevs
authorDavid Howells <dhowells@redhat.com>
Thu, 12 Feb 2009 10:40:00 +0000 (10:40 +0000)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Tue, 24 Mar 2009 09:00:19 +0000 (09:00 +0000)
Present backing device capabilities for MTD character device files to allow
NOMMU mmap to do direct mapping where possible.

Signed-off-by: David Howells <dhowells@redhat.com>
Tested-by: Bernd Schmidt <bernd.schmidt@analog.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
drivers/mtd/Makefile
drivers/mtd/chips/map_ram.c
drivers/mtd/chips/map_rom.c
drivers/mtd/devices/mtdram.c
drivers/mtd/internal.h [new file with mode: 0644]
drivers/mtd/mtdbdi.c [new file with mode: 0644]
drivers/mtd/mtdchar.c
drivers/mtd/mtdcore.c
drivers/mtd/mtdpart.c
include/linux/mtd/mtd.h

index 4521b1e..82d1e4d 100644 (file)
@@ -4,7 +4,7 @@
 
 # Core functionality.
 obj-$(CONFIG_MTD)              += mtd.o
-mtd-y                          := mtdcore.o mtdsuper.o
+mtd-y                          := mtdcore.o mtdsuper.o mtdbdi.o
 mtd-$(CONFIG_MTD_PARTITIONS)   += mtdpart.o
 
 obj-$(CONFIG_MTD_CONCAT)       += mtdconcat.o
index 072dd8a..6bdc50c 100644 (file)
@@ -21,6 +21,8 @@ static int mapram_write (struct mtd_info *, loff_t, size_t, size_t *, const u_ch
 static int mapram_erase (struct mtd_info *, struct erase_info *);
 static void mapram_nop (struct mtd_info *);
 static struct mtd_info *map_ram_probe(struct map_info *map);
+static unsigned long mapram_unmapped_area(struct mtd_info *, unsigned long,
+                                         unsigned long, unsigned long);
 
 
 static struct mtd_chip_driver mapram_chipdrv = {
@@ -64,6 +66,7 @@ static struct mtd_info *map_ram_probe(struct map_info *map)
        mtd->type = MTD_RAM;
        mtd->size = map->size;
        mtd->erase = mapram_erase;
+       mtd->get_unmapped_area = mapram_unmapped_area;
        mtd->read = mapram_read;
        mtd->write = mapram_write;
        mtd->sync = mapram_nop;
@@ -79,6 +82,20 @@ static struct mtd_info *map_ram_probe(struct map_info *map)
 }
 
 
+/*
+ * Allow NOMMU mmap() to directly map the device (if not NULL)
+ * - return the address to which the offset maps
+ * - return -ENOSYS to indicate refusal to do the mapping
+ */
+static unsigned long mapram_unmapped_area(struct mtd_info *mtd,
+                                         unsigned long len,
+                                         unsigned long offset,
+                                         unsigned long flags)
+{
+       struct map_info *map = mtd->priv;
+       return (unsigned long) map->virt + offset;
+}
+
 static int mapram_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
 {
        struct map_info *map = mtd->priv;
index c76d6e5..076090a 100644 (file)
@@ -20,6 +20,8 @@ static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_ch
 static void maprom_nop (struct mtd_info *);
 static struct mtd_info *map_rom_probe(struct map_info *map);
 static int maprom_erase (struct mtd_info *mtd, struct erase_info *info);
+static unsigned long maprom_unmapped_area(struct mtd_info *, unsigned long,
+                                         unsigned long, unsigned long);
 
 static struct mtd_chip_driver maprom_chipdrv = {
        .probe  = map_rom_probe,
@@ -40,6 +42,7 @@ static struct mtd_info *map_rom_probe(struct map_info *map)
        mtd->name = map->name;
        mtd->type = MTD_ROM;
        mtd->size = map->size;
+       mtd->get_unmapped_area = maprom_unmapped_area;
        mtd->read = maprom_read;
        mtd->write = maprom_write;
        mtd->sync = maprom_nop;
@@ -53,6 +56,20 @@ static struct mtd_info *map_rom_probe(struct map_info *map)
 }
 
 
+/*
+ * Allow NOMMU mmap() to directly map the device (if not NULL)
+ * - return the address to which the offset maps
+ * - return -ENOSYS to indicate refusal to do the mapping
+ */
+static unsigned long maprom_unmapped_area(struct mtd_info *mtd,
+                                         unsigned long len,
+                                         unsigned long offset,
+                                         unsigned long flags)
+{
+       struct map_info *map = mtd->priv;
+       return (unsigned long) map->virt + offset;
+}
+
 static int maprom_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
 {
        struct map_info *map = mtd->priv;
index 3aaca88..fce5ff7 100644 (file)
@@ -65,6 +65,19 @@ static void ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
 {
 }
 
+/*
+ * Allow NOMMU mmap() to directly map the device (if not NULL)
+ * - return the address to which the offset maps
+ * - return -ENOSYS to indicate refusal to do the mapping
+ */
+static unsigned long ram_get_unmapped_area(struct mtd_info *mtd,
+                                          unsigned long len,
+                                          unsigned long offset,
+                                          unsigned long flags)
+{
+       return (unsigned long) mtd->priv + offset;
+}
+
 static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
                size_t *retlen, u_char *buf)
 {
@@ -116,6 +129,7 @@ int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
        mtd->erase = ram_erase;
        mtd->point = ram_point;
        mtd->unpoint = ram_unpoint;
+       mtd->get_unmapped_area = ram_get_unmapped_area;
        mtd->read = ram_read;
        mtd->write = ram_write;
 
diff --git a/drivers/mtd/internal.h b/drivers/mtd/internal.h
new file mode 100644 (file)
index 0000000..c658fe7
--- /dev/null
@@ -0,0 +1,17 @@
+/* Internal MTD definitions
+ *
+ * Copyright © 2006 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * 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.
+ */
+
+/*
+ * mtdbdi.c
+ */
+extern struct backing_dev_info mtd_bdi_unmappable;
+extern struct backing_dev_info mtd_bdi_ro_mappable;
+extern struct backing_dev_info mtd_bdi_rw_mappable;
diff --git a/drivers/mtd/mtdbdi.c b/drivers/mtd/mtdbdi.c
new file mode 100644 (file)
index 0000000..5ca5aed
--- /dev/null
@@ -0,0 +1,43 @@
+/* MTD backing device capabilities
+ *
+ * Copyright © 2006 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * 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.
+ */
+
+#include <linux/backing-dev.h>
+#include <linux/mtd/mtd.h>
+#include "internal.h"
+
+/*
+ * backing device capabilities for non-mappable devices (such as NAND flash)
+ * - permits private mappings, copies are taken of the data
+ */
+struct backing_dev_info mtd_bdi_unmappable = {
+       .capabilities   = BDI_CAP_MAP_COPY,
+};
+
+/*
+ * backing device capabilities for R/O mappable devices (such as ROM)
+ * - permits private mappings, copies are taken of the data
+ * - permits non-writable shared mappings
+ */
+struct backing_dev_info mtd_bdi_ro_mappable = {
+       .capabilities   = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT |
+                          BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP),
+};
+
+/*
+ * backing device capabilities for writable mappable devices (such as RAM)
+ * - permits private mappings, copies are taken of the data
+ * - permits non-writable shared mappings
+ */
+struct backing_dev_info mtd_bdi_rw_mappable = {
+       .capabilities   = (BDI_CAP_MAP_COPY | BDI_CAP_MAP_DIRECT |
+                          BDI_CAP_EXEC_MAP | BDI_CAP_READ_MAP |
+                          BDI_CAP_WRITE_MAP),
+};
index 2c8a16f..f478f1f 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/smp_lock.h>
+#include <linux/backing-dev.h>
 
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/compatmac.h>
@@ -107,12 +108,15 @@ static int mtd_open(struct inode *inode, struct file *file)
                goto out;
        }
 
-       if (MTD_ABSENT == mtd->type) {
+       if (mtd->type == MTD_ABSENT) {
                put_mtd_device(mtd);
                ret = -ENODEV;
                goto out;
        }
 
+       if (mtd->backing_dev_info)
+               file->f_mapping->backing_dev_info = mtd->backing_dev_info;
+
        /* You can't open it RW if it's not a writeable device */
        if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
                put_mtd_device(mtd);
@@ -781,6 +785,59 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        return ret;
 } /* memory_ioctl */
 
+/*
+ * try to determine where a shared mapping can be made
+ * - only supported for NOMMU at the moment (MMU can't doesn't copy private
+ *   mappings)
+ */
+#ifndef CONFIG_MMU
+static unsigned long mtd_get_unmapped_area(struct file *file,
+                                          unsigned long addr,
+                                          unsigned long len,
+                                          unsigned long pgoff,
+                                          unsigned long flags)
+{
+       struct mtd_file_info *mfi = file->private_data;
+       struct mtd_info *mtd = mfi->mtd;
+
+       if (mtd->get_unmapped_area) {
+               unsigned long offset;
+
+               if (addr != 0)
+                       return (unsigned long) -EINVAL;
+
+               if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
+                       return (unsigned long) -EINVAL;
+
+               offset = pgoff << PAGE_SHIFT;
+               if (offset > mtd->size - len)
+                       return (unsigned long) -EINVAL;
+
+               return mtd->get_unmapped_area(mtd, len, offset, flags);
+       }
+
+       /* can't map directly */
+       return (unsigned long) -ENOSYS;
+}
+#endif
+
+/*
+ * set up a mapping for shared memory segments
+ */
+static int mtd_mmap(struct file *file, struct vm_area_struct *vma)
+{
+#ifdef CONFIG_MMU
+       struct mtd_file_info *mfi = file->private_data;
+       struct mtd_info *mtd = mfi->mtd;
+
+       if (mtd->type == MTD_RAM || mtd->type == MTD_ROM)
+               return 0;
+       return -ENOSYS;
+#else
+       return vma->vm_flags & VM_SHARED ? 0 : -ENOSYS;
+#endif
+}
+
 static const struct file_operations mtd_fops = {
        .owner          = THIS_MODULE,
        .llseek         = mtd_lseek,
@@ -789,6 +846,10 @@ static const struct file_operations mtd_fops = {
        .ioctl          = mtd_ioctl,
        .open           = mtd_open,
        .release        = mtd_close,
+       .mmap           = mtd_mmap,
+#ifndef CONFIG_MMU
+       .get_unmapped_area = mtd_get_unmapped_area,
+#endif
 };
 
 static int __init init_mtdchar(void)
index 76fe0a1..65a7f37 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/proc_fs.h>
 
 #include <linux/mtd/mtd.h>
+#include "internal.h"
 
 #include "mtdcore.h"
 
@@ -46,6 +47,20 @@ int add_mtd_device(struct mtd_info *mtd)
 {
        int i;
 
+       if (!mtd->backing_dev_info) {
+               switch (mtd->type) {
+               case MTD_RAM:
+                       mtd->backing_dev_info = &mtd_bdi_rw_mappable;
+                       break;
+               case MTD_ROM:
+                       mtd->backing_dev_info = &mtd_bdi_ro_mappable;
+                       break;
+               default:
+                       mtd->backing_dev_info = &mtd_bdi_unmappable;
+                       break;
+               }
+       }
+
        BUG_ON(mtd->writesize == 0);
        mutex_lock(&mtd_table_mutex);
 
index 144e6b6..06d5b9d 100644 (file)
@@ -84,6 +84,18 @@ static void part_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
        part->master->unpoint(part->master, from + part->offset, len);
 }
 
+static unsigned long part_get_unmapped_area(struct mtd_info *mtd,
+                                           unsigned long len,
+                                           unsigned long offset,
+                                           unsigned long flags)
+{
+       struct mtd_part *part = PART(mtd);
+
+       offset += part->offset;
+       return part->master->get_unmapped_area(part->master, len, offset,
+                                              flags);
+}
+
 static int part_read_oob(struct mtd_info *mtd, loff_t from,
                struct mtd_oob_ops *ops)
 {
@@ -342,6 +354,7 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
 
        slave->mtd.name = part->name;
        slave->mtd.owner = master->owner;
+       slave->mtd.backing_dev_info = master->backing_dev_info;
 
        slave->mtd.read = part_read;
        slave->mtd.write = part_write;
@@ -354,6 +367,8 @@ static struct mtd_part *add_one_partition(struct mtd_info *master,
                slave->mtd.unpoint = part_unpoint;
        }
 
+       if (master->get_unmapped_area)
+               slave->mtd.get_unmapped_area = part_get_unmapped_area;
        if (master->read_oob)
                slave->mtd.read_oob = part_read_oob;
        if (master->write_oob)
index 3aa5d77..0c079fd 100644 (file)
@@ -162,6 +162,20 @@ struct mtd_info {
        /* We probably shouldn't allow XIP if the unpoint isn't a NULL */
        void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
 
+       /* Allow NOMMU mmap() to directly map the device (if not NULL)
+        * - return the address to which the offset maps
+        * - return -ENOSYS to indicate refusal to do the mapping
+        */
+       unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
+                                           unsigned long len,
+                                           unsigned long offset,
+                                           unsigned long flags);
+
+       /* Backing device capabilities for this device
+        * - provides mmap capabilities
+        */
+       struct backing_dev_info *backing_dev_info;
+
 
        int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
        int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);