mtd: mtdchar: retry large buffer allocations
authorGrant Erickson <marathon96@gmail.com>
Fri, 8 Apr 2011 15:51:33 +0000 (08:51 -0700)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 25 May 2011 01:00:45 +0000 (02:00 +0100)
Replace direct call to kmalloc for a potentially large, contiguous
buffer allocation with one to mtd_kmalloc_up_to which helps ensure the
operation can succeed under low-memory, highly- fragmented situations
albeit somewhat more slowly.

Signed-off-by: Grant Erickson <marathon96@gmail.com>
Tested-by: Ben Gardiner <bengardiner@nanometrics.ca>
Tested-by: Stefano Babic <sbabic@denx.de>
Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>

drivers/mtd/mtdchar.c

index 4c36ef6..f488eab 100644 (file)
@@ -166,10 +166,23 @@ static int mtd_close(struct inode *inode, struct file *file)
        return 0;
 } /* mtd_close */
 
-/* FIXME: This _really_ needs to die. In 2.5, we should lock the
-   userspace buffer down and use it directly with readv/writev.
-*/
-#define MAX_KMALLOC_SIZE 0x20000
+/* Back in June 2001, dwmw2 wrote:
+ *
+ *   FIXME: This _really_ needs to die. In 2.5, we should lock the
+ *   userspace buffer down and use it directly with readv/writev.
+ *
+ * The implementation below, using mtd_kmalloc_up_to, mitigates
+ * allocation failures when the system is under low-memory situations
+ * or if memory is highly fragmented at the cost of reducing the
+ * performance of the requested transfer due to a smaller buffer size.
+ *
+ * A more complex but more memory-efficient implementation based on
+ * get_user_pages and iovecs to cover extents of those pages is a
+ * longer-term goal, as intimated by dwmw2 above. However, for the
+ * write case, this requires yet more complex head and tail transfer
+ * handling when those head and tail offsets and sizes are such that
+ * alignment requirements are not met in the NAND subdriver.
+ */
 
 static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
 {
@@ -179,6 +192,7 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t
        size_t total_retlen=0;
        int ret=0;
        int len;
+       size_t size = count;
        char *kbuf;
 
        DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
@@ -189,23 +203,12 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t
        if (!count)
                return 0;
 
-       /* FIXME: Use kiovec in 2.5 to lock down the user's buffers
-          and pass them directly to the MTD functions */
-
-       if (count > MAX_KMALLOC_SIZE)
-               kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
-       else
-               kbuf=kmalloc(count, GFP_KERNEL);
-
+       kbuf = mtd_kmalloc_up_to(mtd, &size);
        if (!kbuf)
                return -ENOMEM;
 
        while (count) {
-
-               if (count > MAX_KMALLOC_SIZE)
-                       len = MAX_KMALLOC_SIZE;
-               else
-                       len = count;
+               len = min_t(size_t, count, size);
 
                switch (mfi->mode) {
                case MTD_MODE_OTP_FACTORY:
@@ -268,6 +271,7 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
 {
        struct mtd_file_info *mfi = file->private_data;
        struct mtd_info *mtd = mfi->mtd;
+       size_t size = count;
        char *kbuf;
        size_t retlen;
        size_t total_retlen=0;
@@ -285,20 +289,12 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
        if (!count)
                return 0;
 
-       if (count > MAX_KMALLOC_SIZE)
-               kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
-       else
-               kbuf=kmalloc(count, GFP_KERNEL);
-
+       kbuf = mtd_kmalloc_up_to(mtd, &size);
        if (!kbuf)
                return -ENOMEM;
 
        while (count) {
-
-               if (count > MAX_KMALLOC_SIZE)
-                       len = MAX_KMALLOC_SIZE;
-               else
-                       len = count;
+               len = min_t(size_t, count, size);
 
                if (copy_from_user(kbuf, buf, len)) {
                        kfree(kbuf);