mtd: Fix the behavior of OTP write if there is not enough room for data
authorChristian Riesch <christian.riesch@omicron.at>
Thu, 6 Mar 2014 11:42:37 +0000 (12:42 +0100)
committerBrian Norris <computersforpeace@gmail.com>
Tue, 11 Mar 2014 05:42:31 +0000 (22:42 -0700)
If a write to one time programmable memory (OTP) hits the end of this
memory area, no more data can be written. The count variable in
mtdchar_write() in drivers/mtd/mtdchar.c is not decreased anymore.
We are trapped in the loop forever, mtdchar_write() will never return
in this case.

The desired behavior of a write in such a case is described in [1]:
- Try to write as much data as possible, truncate the write to fit into
  the available memory and return the number of bytes that actually
  have been written.
- If no data could be written at all, return -ENOSPC.

This patch fixes the behavior of OTP write if there is not enough space
for all data:

1) mtd_write_user_prot_reg() in drivers/mtd/mtdcore.c is modified to
   return -ENOSPC if no data could be written at all.
2) mtdchar_write() is modified to handle -ENOSPC correctly. Exit if a
   write returned -ENOSPC and yield the correct return value, either
   then number of bytes that could be written, or -ENOSPC, if no data
   could be written at all.

Furthermore the patch harmonizes the behavior of the OTP memory write
in drivers/mtd/devices/mtd_dataflash.c with the other implementations
and the requirements from [1]. Instead of returning -EINVAL if the data
does not fit into the OTP memory, we try to write as much data as
possible/truncate the write.

[1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html

Signed-off-by: Christian Riesch <christian.riesch@omicron.at>
Signed-off-by: Brian Norris <computersforpeace@gmail.com>
drivers/mtd/devices/mtd_dataflash.c
drivers/mtd/mtdchar.c
drivers/mtd/mtdcore.c

index a6fdbe83bad695e9db20c5c22829652e675bc332..dd22ce2cc9ad6a901d85d28215fcdf0825a0550b 100644 (file)
@@ -542,14 +542,18 @@ static int dataflash_write_user_otp(struct mtd_info *mtd,
        struct dataflash        *priv = mtd->priv;
        int                     status;
 
-       if (len > 64)
-               return -EINVAL;
+       if (from >= 64) {
+               /*
+                * Attempting to write beyond the end of OTP memory,
+                * no data can be written.
+                */
+               *retlen = 0;
+               return 0;
+       }
 
-       /* Strictly speaking, we *could* truncate the write ... but
-        * let's not do that for the only write that's ever possible.
-        */
+       /* Truncate the write to fit into OTP memory. */
        if ((from + len) > 64)
-               return -EINVAL;
+               len = 64 - from;
 
        /* OUT: OP_WRITE_SECURITY, 3 zeroes, 64 data-or-zero bytes
         * IN:  ignore all
index 250798cf76aa548e0a0ffcba8756d988f191f8f8..7d4e7b9da3a1aceef2ea5ea664cea4e0d94752c6 100644 (file)
@@ -324,6 +324,15 @@ static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t c
                default:
                        ret = mtd_write(mtd, *ppos, len, &retlen, kbuf);
                }
+
+               /*
+                * Return -ENOSPC only if no data could be written at all.
+                * Otherwise just return the number of bytes that actually
+                * have been written.
+                */
+               if ((ret == -ENOSPC) && (total_retlen))
+                       break;
+
                if (!ret) {
                        *ppos += retlen;
                        total_retlen += retlen;
index 0a7d77e653359d3aba708ba0e3627922b50287f9..d201feeb3ca6dc7ffe1380840fb0f826ddb0ec10 100644 (file)
@@ -932,12 +932,22 @@ EXPORT_SYMBOL_GPL(mtd_read_user_prot_reg);
 int mtd_write_user_prot_reg(struct mtd_info *mtd, loff_t to, size_t len,
                            size_t *retlen, u_char *buf)
 {
+       int ret;
+
        *retlen = 0;
        if (!mtd->_write_user_prot_reg)
                return -EOPNOTSUPP;
        if (!len)
                return 0;
-       return mtd->_write_user_prot_reg(mtd, to, len, retlen, buf);
+       ret = mtd->_write_user_prot_reg(mtd, to, len, retlen, buf);
+       if (ret)
+               return ret;
+
+       /*
+        * If no data could be written at all, we are out of memory and
+        * must return -ENOSPC.
+        */
+       return (*retlen) ? 0 : -ENOSPC;
 }
 EXPORT_SYMBOL_GPL(mtd_write_user_prot_reg);