All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] UBI: add support for 'SLC mode' volumes
@ 2016-09-05 15:31 Boris Brezillon
  2016-09-05 15:31 ` [PATCH 1/6] UBI: dissociate the on-flash format version and the userspace ABI version Boris Brezillon
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

Hi,

This patch series is adding support for SLC mode volumes.
This is particularly useful on MLC NANDs, where using the NAND in MLC
mode is not reliable.

The first part of the series (patch 1 and 2) are reworking the on-flash
format versioning to support several on-flash format versions with the
same UBI implementation.
Richard, I know you already proposed a solution for the on-flash format
versioning, and after reading the discussion again, I didn't find any
good reason to add the features field we were discussing. Maybe, I'm
wrong, and maybe this is really needed, so feel free to comment on the
current proposal.

The remaining patches prepare the introduction of SLC mode. Note that
we are introducing a new on-flash version (version 2), and SLC mode
volumes are only accepted when the on-flash format version is greater
than 1.

On a side note, this SLC mode and the MLC_SAFE mode we will introduce
later on are quite different. The MLC_SAFE will allow one to use almost
all the MLC NAND capacity in a reliable way, SLC mode only expose half
the real capacity (when used on top of an MLC NAND).

This series depends on the UBI cleanup series [1] I posted earlier
today, and on the MTD pairing scheme [2] series I posted a few weeks
ago.

Regards,

Boris

[1]https://lkml.org/lkml/2016/9/5/459
[2]https://lkml.org/lkml/2016/6/20/459

Boris Brezillon (6):
  UBI: dissociate the on-flash format version and the userspace ABI
    version
  UBI: rework the on-flash format versioning logic
  UBI: use vol->usable_leb_size instead of (ubi->leb_size -
    vol->data_pad)
  UBI: introduce per-volume leb_size
  UBI: io: support SLC mode accesses
  UBI: add SLC mode support

 drivers/mtd/ubi/attach.c    |  59 +++++++--
 drivers/mtd/ubi/build.c     |  10 +-
 drivers/mtd/ubi/cdev.c      |  11 +-
 drivers/mtd/ubi/debug.c     |   4 +
 drivers/mtd/ubi/eba.c       |  64 ++++++++--
 drivers/mtd/ubi/fastmap.c   |   9 +-
 drivers/mtd/ubi/io.c        | 305 ++++++++++++++++++++++++++++++++++++++------
 drivers/mtd/ubi/ubi-media.h |  37 +++++-
 drivers/mtd/ubi/ubi.h       |  99 +++++++++++++-
 drivers/mtd/ubi/upd.c       |   2 +-
 drivers/mtd/ubi/vmt.c       |  41 ++++--
 drivers/mtd/ubi/vtbl.c      |  52 +++++++-
 include/uapi/mtd/ubi-user.h |  24 +++-
 13 files changed, 621 insertions(+), 96 deletions(-)

-- 
2.7.4

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/6] UBI: dissociate the on-flash format version and the userspace ABI version
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  2016-09-05 15:31 ` [PATCH 2/6] UBI: rework the on-flash format versioning logic Boris Brezillon
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

All user-space tools are currently testing the /sys/class/ubi/version value
to make sure they can interact with the in-kernel layer.
They actually don't care about the on-flash format version since this field
is attached to the UBI subsystem and not to each UBI device instance
(theoretically, each instance could have it's own on-flash format version).
All they care about is whether they are able to use the ioctl they know
about.

Create a new UBI_ABI_VERSION to expose the the ABI version instead of
using the UBI_VERSION value which is about to be increased to support
MLC devices.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/build.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 45ea1ddebc5c..914fcf07b573 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -103,11 +103,13 @@ DEFINE_MUTEX(ubi_devices_mutex);
 /* Protects @ubi_devices and @ubi->ref_count */
 static DEFINE_SPINLOCK(ubi_devices_lock);
 
+#define UBI_ABI_VERSION		1
+
 /* "Show" method for files in '/<sysfs>/class/ubi/' */
 static ssize_t ubi_version_show(struct class *class,
 				struct class_attribute *attr, char *buf)
 {
-	return sprintf(buf, "%d\n", UBI_VERSION);
+	return sprintf(buf, "%d\n", UBI_ABI_VERSION);
 }
 
 /* UBI version attribute ('/<sysfs>/class/ubi/version') */
@@ -1490,7 +1492,7 @@ MODULE_PARM_DESC(fm_autoconvert, "Set this parameter to enable fastmap automatic
 module_param(fm_debug, bool, 0);
 MODULE_PARM_DESC(fm_debug, "Set this parameter to enable fastmap debugging by default. Warning, this will make fastmap slow!");
 #endif
-MODULE_VERSION(__stringify(UBI_VERSION));
+MODULE_VERSION(__stringify(UBI_ABI_VERSION));
 MODULE_DESCRIPTION("UBI - Unsorted Block Images");
 MODULE_AUTHOR("Artem Bityutskiy");
 MODULE_LICENSE("GPL");
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/6] UBI: rework the on-flash format versioning logic
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
  2016-09-05 15:31 ` [PATCH 1/6] UBI: dissociate the on-flash format version and the userspace ABI version Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  2016-09-05 15:31 ` [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad) Boris Brezillon
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

Currently, the UBI implementation only supports a single on-flash format
version (version 1).

Rework the version checking/initialization logic to allow the same
implementation to support several on-flash version.

If an empty device is detected, the core picks the latest version
supported by the UBI implementation.

Note that we have moved the version validation code in its own function
so that it can be re-used by both VID and EC header validation.

We also dropped the ech->version check done in scan_peb() since it's
already done when ubi_io_read_ec_hdr() is called.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/attach.c    | 37 +++++++++++++++++++++++++++------
 drivers/mtd/ubi/build.c     |  3 +++
 drivers/mtd/ubi/io.c        | 50 ++++++++++++++++++++++++++++++++++++++++-----
 drivers/mtd/ubi/ubi-media.h |  8 ++++++--
 drivers/mtd/ubi/ubi.h       |  1 +
 5 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index 93ceea4f27d5..48b6f3f8eb40 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -959,6 +959,7 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
 	struct ubi_vid_hdr *vidh = ubi_get_vid_hdr(vidb);
 	long long ec;
 	int err, bitflips = 0, vol_id = -1, ec_err = 0;
+	int version = -1;
 
 	dbg_bld("scan PEB %d", pnum);
 
@@ -1008,12 +1009,8 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
 	if (!ec_err) {
 		int image_seq;
 
-		/* Make sure UBI version is OK */
-		if (ech->version != UBI_VERSION) {
-			ubi_err(ubi, "this UBI version is %d, image version is %d",
-				UBI_VERSION, (int)ech->version);
-			return -EINVAL;
-		}
+		/* Initialize the version value to the EC header one. */
+		version = ech->version;
 
 		ec = be64_to_cpu(ech->ec);
 		if (ec > UBI_MAX_ERASECOUNTER) {
@@ -1142,6 +1139,27 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
 		return -EINVAL;
 	}
 
+	/*
+	 * version might be < 0 if the EC header is corrupted. In this case,
+	 * pick the version found in the VID header.
+	 */
+	if (version < 0)
+		version = vidh->version;
+
+	/* Make sure both VID header and EC header version value match. */
+	if (vidh->version != version) {
+		ubi_err(ubi,
+			"version in VID and EC headers do not match (%d %d)",
+			(int)vidh->version, (int)version);
+	}
+
+	/*
+	 * Initialize the UBI device version if it's the first valid PEB we are
+	 * scanning.
+	 */
+	if (ubi->version < 0)
+		ubi->version = version;
+
 	vol_id = be32_to_cpu(vidh->vol_id);
 	if (vol_id > UBI_MAX_VOLUMES && !vol_ignored(vol_id)) {
 		int lnum = be32_to_cpu(vidh->lnum);
@@ -1268,6 +1286,13 @@ static int late_analysis(struct ubi_device *ubi, struct ubi_attach_info *ai)
 			ubi_msg(ubi, "empty MTD device detected");
 			get_random_bytes(&ubi->image_seq,
 					 sizeof(ubi->image_seq));
+
+			/*
+			 * Initialize the version to the last supported
+			 * version.
+			 */
+			if (ubi->version < 0)
+				ubi->version = UBI_CURRENT_VERSION;
 		} else {
 			ubi_err(ubi, "MTD device is not UBI-formatted and possibly contains non-UBI data - refusing it");
 			return -EINVAL;
diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 914fcf07b573..5b12fa92a695 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -926,6 +926,9 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
 	ubi->vid_hdr_offset = vid_hdr_offset;
 	ubi->autoresize_vol_id = -1;
 
+	/* Initialize the version to -1 to let UBI detect it at attach time. */
+	ubi->version = -1;
+
 #ifdef CONFIG_MTD_UBI_FASTMAP
 	ubi->fm_pool.used = ubi->fm_pool.size = 0;
 	ubi->fm_wl_pool.used = ubi->fm_wl_pool.size = 0;
diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index b6fb8f945c21..6676c21c406b 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -662,6 +662,42 @@ int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum)
 }
 
 /**
+ * validate_version - validate the version number found in the EC or VID
+ *		      header.
+ * @ubi: UBI device description object
+ * @version: version number to check
+ *
+ * This function returns zero if the version is supported by the UBI
+ * implementation and is consistent with previous version numbers found
+ * in other headers.
+ */
+static int validate_version(const struct ubi_device *ubi, int version)
+{
+	if (!UBI_VERSION_IS_SUPPORTED(version)) {
+		unsigned long supports = UBI_SUPPORTED_VERSIONS;
+		int i;
+
+		ubi_err(ubi, "node with incompatible UBI version found");
+		ubi_err(ubi, "\tversion version is %d", version);
+		ubi_err(ubi, "\tthis implementation supports:");
+		for_each_set_bit(i, &supports, 8)
+			ubi_err(ubi, "\t\t%d", i);
+
+		return -EINVAL;
+	}
+
+	if (ubi->version >= 0 && version != ubi->version) {
+		ubi_err(ubi, "node with inconsistent UBI version found");
+		ubi_err(ubi, "\tdevice version is %d", ubi->version);
+		ubi_err(ubi, "\theader version is %d", version);
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
  * validate_ec_hdr - validate an erase counter header.
  * @ubi: UBI device description object
  * @ec_hdr: the erase counter header to check
@@ -679,9 +715,8 @@ static int validate_ec_hdr(const struct ubi_device *ubi,
 	vid_hdr_offset = be32_to_cpu(ec_hdr->vid_hdr_offset);
 	leb_start = be32_to_cpu(ec_hdr->data_offset);
 
-	if (ec_hdr->version != UBI_VERSION) {
-		ubi_err(ubi, "node with incompatible UBI version found: this UBI version is %d, image version is %d",
-			UBI_VERSION, (int)ec_hdr->version);
+	if (validate_version(ubi, ec_hdr->version)) {
+		ubi_err(ubi, "bad version in EC header");
 		goto bad;
 	}
 
@@ -852,7 +887,7 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
 	ubi_assert(pnum >= 0 &&  pnum < ubi->peb_count);
 
 	ec_hdr->magic = cpu_to_be32(UBI_EC_HDR_MAGIC);
-	ec_hdr->version = UBI_VERSION;
+	ec_hdr->version = ubi->version;
 	ec_hdr->vid_hdr_offset = cpu_to_be32(ubi->vid_hdr_offset);
 	ec_hdr->data_offset = cpu_to_be32(ubi->leb_start);
 	ec_hdr->image_seq = cpu_to_be32(ubi->image_seq);
@@ -892,6 +927,11 @@ static int validate_vid_hdr(const struct ubi_device *ubi,
 	int data_crc = be32_to_cpu(vid_hdr->data_crc);
 	int usable_leb_size = ubi->leb_size - data_pad;
 
+	if (validate_version(ubi, vid_hdr->version)) {
+		ubi_err(ubi, "bad version in VID header");
+		goto bad;
+	}
+
 	if (copy_flag != 0 && copy_flag != 1) {
 		ubi_err(ubi, "bad copy_flag");
 		goto bad;
@@ -1110,7 +1150,7 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
 		return err;
 
 	vid_hdr->magic = cpu_to_be32(UBI_VID_HDR_MAGIC);
-	vid_hdr->version = UBI_VERSION;
+	vid_hdr->version = ubi->version;
 	crc = crc32(UBI_CRC32_INIT, vid_hdr, UBI_VID_HDR_SIZE_CRC);
 	vid_hdr->hdr_crc = cpu_to_be32(crc);
 
diff --git a/drivers/mtd/ubi/ubi-media.h b/drivers/mtd/ubi/ubi-media.h
index 22ed3f627506..a536e482b954 100644
--- a/drivers/mtd/ubi/ubi-media.h
+++ b/drivers/mtd/ubi/ubi-media.h
@@ -33,7 +33,10 @@
 #include <asm/byteorder.h>
 
 /* The version of UBI images supported by this implementation */
-#define UBI_VERSION 1
+#define UBI_CURRENT_VERSION		1
+#define UBI_SUPPORTS_VERSION(x)		BIT(x)
+#define UBI_SUPPORTED_VERSIONS		(UBI_SUPPORTS_VERSION(1))
+#define UBI_VERSION_IS_SUPPORTED(x)	(BIT((x)) & UBI_SUPPORTED_VERSIONS)
 
 /* The highest erase counter value supported by this implementation */
 #define UBI_MAX_ERASECOUNTER 0x7FFFFFFF
@@ -170,7 +173,8 @@ struct ubi_ec_hdr {
  * struct ubi_vid_hdr - on-flash UBI volume identifier header.
  * @magic: volume identifier header magic number (%UBI_VID_HDR_MAGIC)
  * @version: UBI implementation version which is supposed to accept this UBI
- *           image (%UBI_VERSION)
+ *           image (see %UBI_SUPPORTED_VERSIONS for the currently supported
+ *           versions)
  * @vol_type: volume type (%UBI_VID_DYNAMIC or %UBI_VID_STATIC)
  * @copy_flag: if this logical eraseblock was copied from another physical
  *             eraseblock (for wear-leveling reasons)
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index a5c509434061..1377e300a118 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -567,6 +567,7 @@ struct ubi_device {
 	spinlock_t volumes_lock;
 	int ref_count;
 	int image_seq;
+	int version;
 
 	int rsvd_pebs;
 	int avail_pebs;
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad)
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
  2016-09-05 15:31 ` [PATCH 1/6] UBI: dissociate the on-flash format version and the userspace ABI version Boris Brezillon
  2016-09-05 15:31 ` [PATCH 2/6] UBI: rework the on-flash format versioning logic Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  2016-09-05 17:42   ` Boris Brezillon
  2016-09-05 15:31 ` [PATCH 4/6] UBI: introduce per-volume leb_size Boris Brezillon
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

vol->usable_size is already set to ubi->leb_size - vol->data_pad. Use
vol->usable_size instead of recalculating it.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/cdev.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
index 72ba84af6698..45c329694a5e 100644
--- a/drivers/mtd/ubi/cdev.c
+++ b/drivers/mtd/ubi/cdev.c
@@ -416,7 +416,7 @@ static long vol_cdev_ioctl(struct file *file, unsigned int cmd,
 		}
 
 		rsvd_bytes = (long long)vol->reserved_pebs *
-					ubi->leb_size-vol->data_pad;
+					vol->usable_leb_size;
 		if (bytes < 0 || bytes > rsvd_bytes) {
 			err = -EINVAL;
 			break;
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/6] UBI: introduce per-volume leb_size
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
                   ` (2 preceding siblings ...)
  2016-09-05 15:31 ` [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad) Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  2016-09-05 15:31 ` [PATCH 5/6] UBI: io: support SLC mode accesses Boris Brezillon
  2016-09-05 15:31 ` [PATCH 6/6] UBI: add SLC mode support Boris Brezillon
  5 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

The LEB size is currently common to all volumes attached to a UBI device,
but the introduction of MLC NAND support and 'MLC safe' dynamic volumes
forces us to tweak the LEB size per volume.
In case of 'MLC safe' volumes, the LEB size should be set to

   (peb_size / bits_per_cells) - ec_and_vid_align_size

This commit only adds a leb_size field to struct ubi_volume and makes use
of it where appropriate.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/eba.c  | 14 +++++++-------
 drivers/mtd/ubi/ubi.h  |  2 ++
 drivers/mtd/ubi/upd.c  |  2 +-
 drivers/mtd/ubi/vmt.c  | 21 ++++++++++++++-------
 drivers/mtd/ubi/vtbl.c |  8 +++++---
 5 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c
index dd270aebe9a1..71c35149175f 100644
--- a/drivers/mtd/ubi/eba.c
+++ b/drivers/mtd/ubi/eba.c
@@ -1205,13 +1205,6 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 
 	dbg_wl("copy LEB %d:%d, PEB %d to PEB %d", vol_id, lnum, from, to);
 
-	if (vid_hdr->vol_type == UBI_VID_STATIC) {
-		data_size = be32_to_cpu(vid_hdr->data_size);
-		aldata_size = ALIGN(data_size, ubi->min_io_size);
-	} else
-		data_size = aldata_size =
-			    ubi->leb_size - be32_to_cpu(vid_hdr->data_pad);
-
 	idx = vol_id2idx(ubi, vol_id);
 	spin_lock(&ubi->volumes_lock);
 	/*
@@ -1261,6 +1254,13 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 		goto out_unlock_leb;
 	}
 
+	if (vid_hdr->vol_type == UBI_VID_STATIC) {
+		data_size = be32_to_cpu(vid_hdr->data_size);
+		aldata_size = ALIGN(data_size, ubi->min_io_size);
+	} else
+		data_size = aldata_size =
+			    vol->leb_size - be32_to_cpu(vid_hdr->data_pad);
+
 	/*
 	 * OK, now the LEB is locked and we can safely start moving it. Since
 	 * this function utilizes the @ubi->peb_buf buffer which is shared
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index 1377e300a118..160cdfd5442f 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -306,6 +306,7 @@ struct ubi_eba_leb_desc {
  *
  * @reserved_pebs: how many physical eraseblocks are reserved for this volume
  * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME)
+ * @leb_size: logical eraseblock size
  * @usable_leb_size: logical eraseblock size without padding
  * @used_ebs: how many logical eraseblocks in this volume contain data
  * @last_eb_bytes: how many bytes are stored in the last logical eraseblock
@@ -355,6 +356,7 @@ struct ubi_volume {
 
 	int reserved_pebs;
 	int vol_type;
+	int leb_size;
 	int usable_leb_size;
 	int used_ebs;
 	int last_eb_bytes;
diff --git a/drivers/mtd/ubi/upd.c b/drivers/mtd/ubi/upd.c
index 0134ba32a057..03fd6ff912cf 100644
--- a/drivers/mtd/ubi/upd.c
+++ b/drivers/mtd/ubi/upd.c
@@ -133,7 +133,7 @@ int ubi_start_update(struct ubi_device *ubi, struct ubi_volume *vol,
 	ubi_assert(!vol->updating && !vol->changing_leb);
 	vol->updating = 1;
 
-	vol->upd_buf = vmalloc(ubi->leb_size);
+	vol->upd_buf = vmalloc(vol->leb_size);
 	if (!vol->upd_buf)
 		return -ENOMEM;
 
diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c
index 21b8c5aca2da..ac703d4631e1 100644
--- a/drivers/mtd/ubi/vmt.c
+++ b/drivers/mtd/ubi/vmt.c
@@ -207,8 +207,17 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 			goto out_unlock;
 		}
 
+	/*
+	 * Volume LEB size is currently PEB size - (size reserved for the EC
+	 * and VID headers). This will change with MLC/TLC NAND support and
+	 * the LEB consolidation concept.
+	 */
+	vol->leb_size = ubi->leb_size;
+
 	/* Calculate how many eraseblocks are requested */
-	vol->usable_leb_size = ubi->leb_size - ubi->leb_size % req->alignment;
+	vol->alignment = req->alignment;
+	vol->data_pad  = vol->leb_size % vol->alignment;
+	vol->usable_leb_size = vol->leb_size - vol->data_pad;
 	vol->reserved_pebs = div_u64(req->bytes + vol->usable_leb_size - 1,
 				     vol->usable_leb_size);
 
@@ -227,8 +236,6 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 	spin_unlock(&ubi->volumes_lock);
 
 	vol->vol_id    = vol_id;
-	vol->alignment = req->alignment;
-	vol->data_pad  = ubi->leb_size % vol->alignment;
 	vol->vol_type  = req->vol_type;
 	vol->name_len  = req->name_len;
 	memcpy(vol->name, req->name, vol->name_len);
@@ -675,7 +682,7 @@ static int self_check_volume(struct ubi_device *ubi, int vol_id)
 		ubi_err(ubi, "negative values");
 		goto fail;
 	}
-	if (vol->alignment > ubi->leb_size || vol->alignment == 0) {
+	if (vol->alignment > vol->leb_size || vol->alignment == 0) {
 		ubi_err(ubi, "bad alignment");
 		goto fail;
 	}
@@ -686,7 +693,7 @@ static int self_check_volume(struct ubi_device *ubi, int vol_id)
 		goto fail;
 	}
 
-	n = ubi->leb_size % vol->alignment;
+	n = vol->leb_size % vol->alignment;
 	if (vol->data_pad != n) {
 		ubi_err(ubi, "bad data_pad, has to be %lld", n);
 		goto fail;
@@ -708,8 +715,8 @@ static int self_check_volume(struct ubi_device *ubi, int vol_id)
 		goto fail;
 	}
 
-	n = ubi->leb_size - vol->data_pad;
-	if (vol->usable_leb_size != ubi->leb_size - vol->data_pad) {
+	n = vol->leb_size - vol->data_pad;
+	if (vol->usable_leb_size != n) {
 		ubi_err(ubi, "bad usable_leb_size, has to be %lld", n);
 		goto fail;
 	}
diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c
index 263743e7b741..4ed75a03e363 100644
--- a/drivers/mtd/ubi/vtbl.c
+++ b/drivers/mtd/ubi/vtbl.c
@@ -555,7 +555,8 @@ static int init_volumes(struct ubi_device *ubi,
 		vol->vol_type = vtbl[i].vol_type == UBI_VID_DYNAMIC ?
 					UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME;
 		vol->name_len = be16_to_cpu(vtbl[i].name_len);
-		vol->usable_leb_size = ubi->leb_size - vol->data_pad;
+		vol->leb_size = ubi->leb_size;
+		vol->usable_leb_size = vol->leb_size - vol->data_pad;
 		memcpy(vol->name, vtbl[i].name, vol->name_len);
 		vol->name[vol->name_len] = '\0';
 		vol->vol_id = i;
@@ -632,11 +633,12 @@ static int init_volumes(struct ubi_device *ubi,
 	vol->vol_type = UBI_DYNAMIC_VOLUME;
 	vol->name_len = sizeof(UBI_LAYOUT_VOLUME_NAME) - 1;
 	memcpy(vol->name, UBI_LAYOUT_VOLUME_NAME, vol->name_len + 1);
-	vol->usable_leb_size = ubi->leb_size;
 	vol->used_ebs = vol->reserved_pebs;
 	vol->last_eb_bytes = vol->reserved_pebs;
+	vol->leb_size = ubi->leb_size;
+	vol->usable_leb_size = vol->leb_size;
 	vol->used_bytes =
-		(long long)vol->used_ebs * (ubi->leb_size - vol->data_pad);
+		(long long)vol->used_ebs * (vol->leb_size - vol->data_pad);
 	vol->vol_id = UBI_LAYOUT_VOLUME_ID;
 	vol->ref_count = 1;
 
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/6] UBI: io: support SLC mode accesses
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
                   ` (3 preceding siblings ...)
  2016-09-05 15:31 ` [PATCH 4/6] UBI: introduce per-volume leb_size Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  2016-09-05 15:31 ` [PATCH 6/6] UBI: add SLC mode support Boris Brezillon
  5 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

MLC and TLC NANDs are bringing new constraints, one of them is the concept
of "page pairing".

In MLC NANDs pages are grouped together to form what is usually called a
"pair". These pages are actually using the same cells, and when you program
the second page of a pair and this operation is interrupted, you will not
only corrupt the content of the page you were programming, but also the
content of the page paired with it.
The same goes for TLC NANDs, except that you will have 3 pages grouped
together instead of 2.

In order to overcome this problem, we introduce a new programming/reading
mode, called SLC mode. In this mode only the first page of each pair is
programmed, so that, when a power-cut occurs, only the page that was being
programmed at that time is corrupted. Which is what UBI users expect.

The patch only introduces the new I/O methods, but does not use them yet.
In order to efficiently use them we will need to store/extract the
'programming mode' information from somewhere (VID header).

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/attach.c  |   5 +-
 drivers/mtd/ubi/build.c   |   1 +
 drivers/mtd/ubi/eba.c     |  21 +++--
 drivers/mtd/ubi/fastmap.c |   9 +-
 drivers/mtd/ubi/io.c      | 218 +++++++++++++++++++++++++++++++++++++++-------
 drivers/mtd/ubi/ubi.h     |  36 ++++++--
 drivers/mtd/ubi/vtbl.c    |   7 +-
 7 files changed, 243 insertions(+), 54 deletions(-)

diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index 48b6f3f8eb40..aa82ff23e1d6 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -523,7 +523,8 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
 	len = be32_to_cpu(vid_hdr->data_size);
 
 	mutex_lock(&ubi->buf_mutex);
-	err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, len);
+	err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, len,
+			       UBI_IO_MODE_NORMAL);
 	if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err))
 		goto out_unlock;
 
@@ -891,7 +892,7 @@ static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr,
 	memset(ubi->peb_buf, 0x00, ubi->leb_size);
 
 	err = ubi_io_read(ubi, ubi->peb_buf, pnum, ubi->leb_start,
-			  ubi->leb_size);
+			  ubi->leb_size, UBI_IO_MODE_NORMAL);
 	if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) {
 		/*
 		 * Bit-flips or integrity errors while reading the data area.
diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 5b12fa92a695..c0bf26e6f856 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -654,6 +654,7 @@ static int io_init(struct ubi_device *ubi, int max_beb_per1024)
 
 	ubi->peb_size   = ubi->mtd->erasesize;
 	ubi->peb_count  = mtd_div_by_eb(ubi->mtd->size, ubi->mtd);
+	ubi->max_lebs_per_peb = mtd_pairing_groups_per_eb(ubi->mtd);
 	ubi->flash_size = ubi->mtd->size;
 
 	if (mtd_can_have_bb(ubi->mtd)) {
diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c
index 71c35149175f..4424af7ea9ac 100644
--- a/drivers/mtd/ubi/eba.c
+++ b/drivers/mtd/ubi/eba.c
@@ -601,7 +601,8 @@ retry:
 		ubi_free_vid_buf(vidb);
 	}
 
-	err = ubi_io_read_data(ubi, buf, pnum, offset, len);
+	err = ubi_io_read_data(ubi, buf, pnum, offset, len,
+			       UBI_IO_MODE_NORMAL);
 	if (err) {
 		if (err == UBI_IO_BITFLIPS)
 			scrub = 1;
@@ -743,7 +744,8 @@ static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum,
 
 	/* Read everything before the area where the write failure happened */
 	if (offset > 0) {
-		err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset);
+		err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset,
+				       UBI_IO_MODE_NORMAL);
 		if (err && err != UBI_IO_BITFLIPS)
 			goto out_unlock;
 	}
@@ -760,7 +762,8 @@ static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum,
 	if (err)
 		goto out_unlock;
 
-	err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size);
+	err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size,
+				UBI_IO_MODE_NORMAL);
 
 out_unlock:
 	mutex_unlock(&ubi->buf_mutex);
@@ -867,7 +870,8 @@ static int try_write_vid_and_data(struct ubi_volume *vol, int lnum,
 	}
 
 	if (len) {
-		err = ubi_io_write_data(ubi, buf, pnum, offset, len);
+		err = ubi_io_write_data(ubi, buf, pnum, offset, len,
+					UBI_IO_MODE_NORMAL);
 		if (err) {
 			ubi_warn(ubi,
 				 "failed to write %d bytes at offset %d of LEB %d:%d, PEB %d",
@@ -923,7 +927,8 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
 		dbg_eba("write %d bytes at offset %d of LEB %d:%d, PEB %d",
 			len, offset, vol_id, lnum, pnum);
 
-		err = ubi_io_write_data(ubi, buf, pnum, offset, len);
+		err = ubi_io_write_data(ubi, buf, pnum, offset, len,
+					UBI_IO_MODE_NORMAL);
 		if (err) {
 			ubi_warn(ubi, "failed to write data to PEB %d", pnum);
 			if (err == -EIO && ubi->bad_allowed)
@@ -1269,7 +1274,8 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 	 */
 	mutex_lock(&ubi->buf_mutex);
 	dbg_wl("read %d bytes of data", aldata_size);
-	err = ubi_io_read_data(ubi, ubi->peb_buf, from, 0, aldata_size);
+	err = ubi_io_read_data(ubi, ubi->peb_buf, from, 0, aldata_size,
+			       UBI_IO_MODE_NORMAL);
 	if (err && err != UBI_IO_BITFLIPS) {
 		ubi_warn(ubi, "error %d while reading data from PEB %d",
 			 err, from);
@@ -1331,7 +1337,8 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 	}
 
 	if (data_size > 0) {
-		err = ubi_io_write_data(ubi, ubi->peb_buf, to, 0, aldata_size);
+		err = ubi_io_write_data(ubi, ubi->peb_buf, to, 0, aldata_size,
+					UBI_IO_MODE_NORMAL);
 		if (err) {
 			if (err == -EIO)
 				err = MOVE_TARGET_WR_ERR;
diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c
index 4adffb893376..c44cdfdf130f 100644
--- a/drivers/mtd/ubi/fastmap.c
+++ b/drivers/mtd/ubi/fastmap.c
@@ -876,7 +876,8 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
 		goto out;
 	}
 
-	ret = ubi_io_read_data(ubi, fmsb, fm_anchor, 0, sizeof(*fmsb));
+	ret = ubi_io_read_data(ubi, fmsb, fm_anchor, 0, sizeof(*fmsb),
+			       UBI_IO_MODE_NORMAL);
 	if (ret && ret != UBI_IO_BITFLIPS)
 		goto free_fm_sb;
 	else if (ret == UBI_IO_BITFLIPS)
@@ -997,7 +998,8 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
 			sqnum = be64_to_cpu(vh->sqnum);
 
 		ret = ubi_io_read_data(ubi, ubi->fm_buf + (ubi->leb_size * i),
-				       pnum, 0, ubi->leb_size);
+				       pnum, 0, ubi->leb_size,
+				       UBI_IO_MODE_NORMAL);
 		if (ret && ret != UBI_IO_BITFLIPS) {
 			ubi_err(ubi, "unable to read fastmap block# %i (PEB: %i, "
 				"err: %i)", i, pnum, ret);
@@ -1320,7 +1322,8 @@ static int ubi_write_fastmap(struct ubi_device *ubi,
 
 	for (i = 0; i < new_fm->used_blocks; i++) {
 		ret = ubi_io_write_data(ubi, fm_raw + (i * ubi->leb_size),
-					new_fm->e[i]->pnum, 0, ubi->leb_size);
+					new_fm->e[i]->pnum, 0, ubi->leb_size,
+					UBI_IO_MODE_NORMAL);
 		if (ret) {
 			ubi_err(ubi, "unable to write fastmap to PEB %i!",
 				new_fm->e[i]->pnum);
diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index 6676c21c406b..329ad430473b 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -99,15 +99,85 @@ static int self_check_peb_vid_hdr(const struct ubi_device *ubi, int pnum);
 static int self_check_vid_hdr(const struct ubi_device *ubi, int pnum,
 			      const struct ubi_vid_hdr *vid_hdr);
 static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum,
-			    int offset, int len);
+			    int offset, int len, enum ubi_io_mode mode);
+
 
 /**
- * ubi_io_read - read data from a physical eraseblock.
+ * ubi_io_mtd_read - wrapper around mtd_read() to support normal and SLC reads.
  * @ubi: UBI device description object
  * @buf: buffer where to store the read data
  * @pnum: physical eraseblock number to read from
  * @offset: offset within the physical eraseblock from where to read
  * @len: how many bytes to read
+ * @read: number of bytes successfully read from the underlying MTD device
+ * @mode: I/O mode
+ *
+ * This function is a simple wrapper around mtd_read().
+ * When operating in normal mode, mtd_read() is called directly.
+ * When operating in SLC mode, the offset is adapted, and only the first page
+ * each pair is read.
+ */
+static int ubi_io_mtd_read(const struct ubi_device *ubi, void *buf, int pnum,
+			   int offset, int len, size_t *read,
+			   enum ubi_io_mode mode)
+{
+	loff_t addr = (loff_t)pnum * ubi->peb_size;
+	int wunitoffs, chunklen, err = 0, end = offset + len;
+	struct mtd_pairing_info info;
+
+	/*
+	 * Call mtd_read() directly if we're doing non-SLC read or interacting
+	 * with an SLC chip.
+	 */
+	if (mode == UBI_IO_MODE_NORMAL || ubi->max_lebs_per_peb == 1)
+		return mtd_read(ubi->mtd, addr + offset, len, read, buf);
+
+	wunitoffs = offset % ubi->mtd->writesize;
+	info.pair = offset / ubi->mtd->writesize;
+	info.group = 0;
+	*read = 0;
+
+	while (offset < end) {
+		int realoffs, ret;
+		size_t chunkread = 0;
+
+		chunklen = min_t(int, ubi->mtd->writesize - wunitoffs,
+				 end - offset);
+		realoffs = mtd_pairing_info_to_wunit(ubi->mtd, &info);
+		realoffs *= ubi->mtd->writesize;
+		realoffs += wunitoffs;
+		ret = mtd_read(ubi->mtd, addr + realoffs, chunklen,
+			       &chunkread, buf);
+		*read += chunkread;
+		if (mtd_is_bitflip(ret)) {
+			if (!err)
+				err = -EUCLEAN;
+		} else if (mtd_is_eccerr(ret)) {
+			err = -EBADMSG;
+		} else if (ret) {
+			return ret;
+		}
+
+		offset += chunklen;
+		buf += chunklen;
+		info.pair++;
+
+		if (wunitoffs)
+			wunitoffs = 0;
+	}
+
+	return err;
+}
+
+/**
+ * ubi_io_read - read data from a physical eraseblock
+ * @ubi: UBI device description object
+ * @buf: buffer where to store the read data
+ * @pnum: physical eraseblock number to read from
+ * @offset: offset within the physical eraseblock from where to read
+ * @len: how many bytes to read
+ * @mode: I/O mode to use for this read. Only supports %UBI_IO_MODE_NORMAL and
+ *	  %UBI_IO_MODE_SLC for now.
  *
  * This function reads data from offset @offset of physical eraseblock @pnum
  * and stores the read data in the @buf buffer. The following return codes are
@@ -124,16 +194,19 @@ static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum,
  * o other negative error codes in case of other errors.
  */
 int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
-		int len)
+		int len, enum ubi_io_mode mode)
 {
-	int err, retries = 0;
+	int err, max_size, retries = 0;
 	size_t read;
-	loff_t addr;
+
+	max_size = ubi->peb_size;
+	if (mode == UBI_IO_MODE_SLC)
+		max_size /= ubi->max_lebs_per_peb;
 
 	dbg_io("read %d bytes from PEB %d:%d", len, pnum, offset);
 
 	ubi_assert(pnum >= 0 && pnum < ubi->peb_count);
-	ubi_assert(offset >= 0 && offset + len <= ubi->peb_size);
+	ubi_assert(offset >= 0 && offset + len <= max_size);
 	ubi_assert(len > 0);
 
 	err = self_check_not_bad(ubi, pnum);
@@ -162,9 +235,8 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
 	 */
 	*((uint8_t *)buf) ^= 0xFF;
 
-	addr = (loff_t)pnum * ubi->peb_size + offset;
 retry:
-	err = mtd_read(ubi->mtd, addr, len, &read, buf);
+	err = ubi_io_mtd_read(ubi, buf, pnum, offset, len, &read, mode);
 	if (err) {
 		const char *errstr = mtd_is_eccerr(err) ? " (ECC error)" : "";
 
@@ -216,12 +288,68 @@ retry:
 }
 
 /**
- * ubi_io_write - write data to a physical eraseblock.
+ * ubi_io_mtd_write - wrapper around mtd_write() to support normal and SLC
+ *		      modes.
  * @ubi: UBI device description object
  * @buf: buffer with the data to write
  * @pnum: physical eraseblock number to write to
  * @offset: offset within the physical eraseblock where to write
  * @len: how many bytes to write
+ * @mode: I/O mode
+ *
+ * This function is a simple wrapper around mtd_write().
+ * When operating in normal mode, mtd_write() is called directly.
+ * When operating in SLC mode, the offset is adapted, and only the first page
+ * each pair is written, so that a power-cut happening in the middle of a write
+ * operation can only corrupt a single page (the one being written). This is
+ * what we call SLC mode (it's actually emulating the SLC NAND behavior).
+ */
+static int ubi_io_mtd_write(struct ubi_device *ubi, const void *buf, int pnum,
+			    int offset, int len, size_t *written,
+			    enum ubi_io_mode mode)
+{
+	loff_t addr = (loff_t)pnum * ubi->peb_size;
+	int chunklen, err = 0, end = offset + len;
+	struct mtd_pairing_info info;
+
+	if (mode == UBI_IO_MODE_NORMAL || ubi->max_lebs_per_peb == 1)
+		return mtd_write(ubi->mtd, addr + offset, len, written, buf);
+
+	info.pair = offset / ubi->mtd->writesize;
+	info.group = 0;
+	*written = 0;
+
+	while (offset < end) {
+		int realoffs;
+		size_t chunkwritten = 0;
+
+		chunklen = min_t(int, ubi->mtd->writesize,
+				 end - offset);
+		realoffs = mtd_pairing_info_to_wunit(ubi->mtd, &info);
+		realoffs *= ubi->mtd->writesize;
+		err = mtd_write(ubi->mtd, addr + realoffs, chunklen,
+				&chunkwritten, buf);
+		*written += chunkwritten;
+		if (err && !mtd_is_bitflip(err))
+			return err;
+
+		offset += chunklen;
+		buf += chunklen;
+		info.pair++;
+	}
+
+	return err;
+}
+
+/**
+ * ubi_io_write - write data to a physical eraseblock
+ * @ubi: UBI device description object
+ * @buf: buffer with the data to write
+ * @pnum: physical eraseblock number to write to
+ * @offset: offset within the physical eraseblock where to write
+ * @len: how many bytes to write
+ * mode: I/O mode to use for this write. Only supports %UBI_IO_MODE_NORMAL and
+ *	 %UBI_IO_MODE_SLC for now.
  *
  * This function writes @len bytes of data from buffer @buf to offset @offset
  * of physical eraseblock @pnum. If all the data were successfully written,
@@ -233,16 +361,20 @@ retry:
  * to the flash media, but may be some garbage.
  */
 int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
-		 int len)
+		 int len, enum ubi_io_mode mode)
 {
+	int max_size, rawoffs = offset, rawlen = len;
 	int err;
 	size_t written;
-	loff_t addr;
+
+	max_size = ubi->peb_size;
+	if (mode == UBI_IO_MODE_SLC)
+		max_size /= ubi->max_lebs_per_peb;
 
 	dbg_io("write %d bytes to PEB %d:%d", len, pnum, offset);
 
 	ubi_assert(pnum >= 0 && pnum < ubi->peb_count);
-	ubi_assert(offset >= 0 && offset + len <= ubi->peb_size);
+	ubi_assert(offset >= 0 && offset + len <= max_size);
 	ubi_assert(offset % ubi->hdrs_min_io_size == 0);
 	ubi_assert(len > 0 && len % ubi->hdrs_min_io_size == 0);
 
@@ -255,8 +387,23 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
 	if (err)
 		return err;
 
+	if (mode == UBI_IO_MODE_SLC && ubi->max_lebs_per_peb > 1) {
+		struct mtd_pairing_info info;
+
+		info.pair = offset / ubi->mtd->writesize;
+		info.group = 0;
+		rawoffs = mtd_pairing_info_to_wunit(ubi->mtd, &info);
+		rawoffs *= ubi->mtd->writesize;
+
+		info.pair = (offset + len) / ubi->mtd->writesize;
+		info.group = 0;
+		rawlen = mtd_pairing_info_to_wunit(ubi->mtd, &info);
+		rawlen *= ubi->mtd->writesize;
+		rawlen -= rawoffs;
+	}
+
 	/* The area we are writing to has to contain all 0xFF bytes */
-	err = ubi_self_check_all_ff(ubi, pnum, offset, len);
+	err = ubi_self_check_all_ff(ubi, pnum, rawoffs, rawlen);
 	if (err)
 		return err;
 
@@ -280,8 +427,7 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
 		return -EIO;
 	}
 
-	addr = (loff_t)pnum * ubi->peb_size + offset;
-	err = mtd_write(ubi->mtd, addr, len, &written, buf);
+	err = ubi_io_mtd_write(ubi, buf, pnum, offset, len, &written, mode);
 	if (err) {
 		ubi_err(ubi, "error %d while writing %d bytes to PEB %d:%d, written %zd bytes",
 			err, len, pnum, offset, written);
@@ -291,7 +437,7 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
 		ubi_assert(written == len);
 
 	if (!err) {
-		err = self_check_write(ubi, buf, pnum, offset, len);
+		err = self_check_write(ubi, buf, pnum, offset, len, mode);
 		if (err)
 			return err;
 
@@ -299,10 +445,11 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
 		 * Since we always write sequentially, the rest of the PEB has
 		 * to contain only 0xFF bytes.
 		 */
-		offset += len;
-		len = ubi->peb_size - offset;
-		if (len)
-			err = ubi_self_check_all_ff(ubi, pnum, offset, len);
+		rawoffs += rawlen;
+		rawlen = ubi->peb_size - rawoffs;
+		if (rawlen)
+			err = ubi_self_check_all_ff(ubi, pnum, rawoffs,
+						    rawlen);
 	}
 
 	return err;
@@ -424,7 +571,8 @@ static int torture_peb(struct ubi_device *ubi, int pnum)
 			goto out;
 
 		/* Make sure the PEB contains only 0xFF bytes */
-		err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size);
+		err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size,
+				  UBI_IO_MODE_NORMAL);
 		if (err)
 			goto out;
 
@@ -438,12 +586,14 @@ static int torture_peb(struct ubi_device *ubi, int pnum)
 
 		/* Write a pattern and check it */
 		memset(ubi->peb_buf, patterns[i], ubi->peb_size);
-		err = ubi_io_write(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size);
+		err = ubi_io_write(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size,
+				   UBI_IO_MODE_NORMAL);
 		if (err)
 			goto out;
 
 		memset(ubi->peb_buf, ~patterns[i], ubi->peb_size);
-		err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size);
+		err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size,
+				  UBI_IO_MODE_NORMAL);
 		if (err)
 			goto out;
 
@@ -777,7 +927,8 @@ int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum,
 	dbg_io("read EC header from PEB %d", pnum);
 	ubi_assert(pnum >= 0 && pnum < ubi->peb_count);
 
-	read_err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE);
+	read_err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE,
+			       UBI_IO_MODE_NORMAL);
 	if (read_err) {
 		if (read_err != UBI_IO_BITFLIPS && !mtd_is_eccerr(read_err))
 			return read_err;
@@ -901,7 +1052,8 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
 	if (ubi_dbg_power_cut(ubi, POWER_CUT_EC_WRITE))
 		return -EROFS;
 
-	err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
+	err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize,
+			   UBI_IO_MODE_NORMAL);
 	return err;
 }
 
@@ -1062,7 +1214,8 @@ int ubi_io_read_vid_hdr(struct ubi_device *ubi, int pnum,
 	ubi_assert(pnum >= 0 &&  pnum < ubi->peb_count);
 
 	read_err = ubi_io_read(ubi, p, pnum, ubi->vid_hdr_aloffset,
-			  ubi->vid_hdr_shift + UBI_VID_HDR_SIZE);
+			       ubi->vid_hdr_shift + UBI_VID_HDR_SIZE,
+			       UBI_IO_MODE_NORMAL);
 	if (read_err && read_err != UBI_IO_BITFLIPS && !mtd_is_eccerr(read_err))
 		return read_err;
 
@@ -1162,7 +1315,7 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
 		return -EROFS;
 
 	err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
-			   ubi->vid_hdr_alsize);
+			   ubi->vid_hdr_alsize, UBI_IO_MODE_NORMAL);
 	return err;
 }
 
@@ -1250,7 +1403,8 @@ static int self_check_peb_ec_hdr(const struct ubi_device *ubi, int pnum)
 	if (!ec_hdr)
 		return -ENOMEM;
 
-	err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE);
+	err = ubi_io_read(ubi, ec_hdr, pnum, 0, UBI_EC_HDR_SIZE,
+			  UBI_IO_MODE_NORMAL);
 	if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err))
 		goto exit;
 
@@ -1340,7 +1494,7 @@ static int self_check_peb_vid_hdr(const struct ubi_device *ubi, int pnum)
 	vid_hdr = ubi_get_vid_hdr(vidb);
 	p = vidb->buffer;
 	err = ubi_io_read(ubi, p, pnum, ubi->vid_hdr_aloffset,
-			  ubi->vid_hdr_alsize);
+			  ubi->vid_hdr_alsize, UBI_IO_MODE_NORMAL);
 	if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err))
 		goto exit;
 
@@ -1376,12 +1530,10 @@ exit:
  * match and a negative error code if not or in case of failure.
  */
 static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum,
-			    int offset, int len)
+			    int offset, int len, enum ubi_io_mode mode)
 {
 	int err, i;
-	size_t read;
 	void *buf1;
-	loff_t addr = (loff_t)pnum * ubi->peb_size + offset;
 
 	if (!ubi_dbg_chk_io(ubi))
 		return 0;
@@ -1392,7 +1544,7 @@ static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum,
 		return 0;
 	}
 
-	err = mtd_read(ubi->mtd, addr, len, &read, buf1);
+	err = ubi_io_read(ubi, buf1, pnum, offset, len, mode);
 	if (err && !mtd_is_bitflip(err))
 		goto out_free;
 
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index 160cdfd5442f..de4c65cb8f7a 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -166,6 +166,24 @@ enum {
 	POWER_CUT_VID_WRITE = 0x02,
 };
 
+/*
+ * IO modes.
+ *
+ * UBI_IO_MODE_NORMAL: Normal mode. For everything but MLC/TLC NANDs this is
+ *		       the only available mode. For MLC/TLC NANDs, data are
+ *		       read/written normally, without taking any precaution to
+ *		       ensure its reliability.
+ * UBI_IO_MODE_SLC: For everything but MLC/TLC NANDs passing this option as the
+ *		    same effect as passing %UBI_IO_MODE_NORMAL. For MLC/TLC
+ *		    NANDs this mode emulate the behavior of an SLC NAND by only
+ *		    writing part of the erase block to avoid 'paired page'
+ *		    corruption.
+ */
+enum ubi_io_mode {
+	UBI_IO_MODE_NORMAL,
+	UBI_IO_MODE_SLC,
+};
+
 /**
  * struct ubi_vid_io_buf - VID buffer used to read/write VID info to/from the
  *			   flash.
@@ -526,6 +544,9 @@ struct ubi_debug_info {
  * @flash_size: underlying MTD device size (in bytes)
  * @peb_count: count of physical eraseblocks on the MTD device
  * @peb_size: physical eraseblock size
+ * @max_lebs_per_peb: the maximum number of LEBs per PEB. Should always be 1
+ *		      except for MLC or TLC NANDs, where it should be equal
+ *		      to the number of bits per cell.
  * @bad_peb_limit: top limit of expected bad physical eraseblocks
  * @bad_peb_count: count of bad physical eraseblocks
  * @good_peb_count: count of good physical eraseblocks
@@ -632,6 +653,7 @@ struct ubi_device {
 	long long flash_size;
 	int peb_count;
 	int peb_size;
+	int max_lebs_per_peb;
 	int bad_peb_count;
 	int good_peb_count;
 	int corr_peb_count;
@@ -925,9 +947,9 @@ int ubi_ensure_anchor_pebs(struct ubi_device *ubi);
 
 /* io.c */
 int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset,
-		int len);
+		int len, enum ubi_io_mode mode);
 int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset,
-		 int len);
+		 int len, enum ubi_io_mode mode);
 int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture);
 int ubi_io_is_bad(const struct ubi_device *ubi, int pnum);
 int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum);
@@ -1129,10 +1151,11 @@ static inline struct ubi_vid_hdr *ubi_get_vid_hdr(struct ubi_vid_io_buf *vidb)
  * physical eraseblock.
  */
 static inline int ubi_io_read_data(const struct ubi_device *ubi, void *buf,
-				   int pnum, int offset, int len)
+				   int pnum, int offset, int len,
+				   enum ubi_io_mode mode)
 {
 	ubi_assert(offset >= 0);
-	return ubi_io_read(ubi, buf, pnum, offset + ubi->leb_start, len);
+	return ubi_io_read(ubi, buf, pnum, offset + ubi->leb_start, len, mode);
 }
 
 /*
@@ -1141,10 +1164,11 @@ static inline int ubi_io_read_data(const struct ubi_device *ubi, void *buf,
  * physical eraseblock.
  */
 static inline int ubi_io_write_data(struct ubi_device *ubi, const void *buf,
-				    int pnum, int offset, int len)
+				    int pnum, int offset, int len,
+				    enum ubi_io_mode mode)
 {
 	ubi_assert(offset >= 0);
-	return ubi_io_write(ubi, buf, pnum, offset + ubi->leb_start, len);
+	return ubi_io_write(ubi, buf, pnum, offset + ubi->leb_start, len, mode);
 }
 
 /**
diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c
index 4ed75a03e363..8f4abecdae59 100644
--- a/drivers/mtd/ubi/vtbl.c
+++ b/drivers/mtd/ubi/vtbl.c
@@ -332,7 +332,8 @@ retry:
 		goto write_error;
 
 	/* Write the layout volume contents */
-	err = ubi_io_write_data(ubi, vtbl, new_aeb->pnum, 0, ubi->vtbl_size);
+	err = ubi_io_write_data(ubi, vtbl, new_aeb->pnum, 0, ubi->vtbl_size,
+				UBI_IO_MODE_NORMAL);
 	if (err)
 		goto write_error;
 
@@ -416,8 +417,8 @@ static struct ubi_vtbl_record *process_lvol(struct ubi_device *ubi,
 			goto out_free;
 		}
 
-		err = ubi_io_read_data(ubi, leb[aeb->lnum], aeb->pnum, 0,
-				       ubi->vtbl_size);
+		err = ubi_io_read_data(ubi, leb[aeb->lnum], aeb->pnum,
+				       0, ubi->vtbl_size, UBI_IO_MODE_NORMAL);
 		if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err))
 			/*
 			 * Scrub the PEB later. Note, -EBADMSG indicates an
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 6/6] UBI: add SLC mode support
  2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
                   ` (4 preceding siblings ...)
  2016-09-05 15:31 ` [PATCH 5/6] UBI: io: support SLC mode accesses Boris Brezillon
@ 2016-09-05 15:31 ` Boris Brezillon
  5 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 15:31 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel, Boris Brezillon

On MLC/TLC NANDs, this SLC mode support allows one to use the underlying
MTD device in SLC mode. This is not a real SLC mode, as is sometime
provided by NAND vendors, but we emulate SLC mode by programming only
one page of each pair.

This mode is optional and is selected at volume creation time.

The layout volume is automatically switched to an SLC mode volume upon
creation, since keeping it in SLC mode does not bring any advantage.

Fastmap is kept in normal modes for now.

The ubi_attach_req struct, which is exposed to userspace, is also updated,
but the previous version is still working fine, except only normal volumes
will be supported in this case.

We also update the UBI on-flash format version (switching to version 2),
and only accept SLC mode when the UBI image is set to version 2. This
way the implementation stay compatible with version 1 images.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/mtd/ubi/attach.c    | 21 +++++++++++++---
 drivers/mtd/ubi/cdev.c      |  9 +++++++
 drivers/mtd/ubi/debug.c     |  4 +++
 drivers/mtd/ubi/eba.c       | 47 ++++++++++++++++++++++++++++-------
 drivers/mtd/ubi/io.c        | 37 ++++++++++++++++++++++++++++
 drivers/mtd/ubi/ubi-media.h | 33 ++++++++++++++++++++++---
 drivers/mtd/ubi/ubi.h       | 60 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/mtd/ubi/vmt.c       | 32 ++++++++++++++++++------
 drivers/mtd/ubi/vtbl.c      | 45 ++++++++++++++++++++++++++++++----
 include/uapi/mtd/ubi-user.h | 24 ++++++++++++++++--
 10 files changed, 281 insertions(+), 31 deletions(-)

diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index aa82ff23e1d6..184da5020d37 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -149,6 +149,7 @@ static struct ubi_ainf_volume *find_or_add_av(struct ubi_attach_info *ai,
 		return ERR_PTR(-ENOMEM);
 
 	av->vol_id = vol_id;
+	av->vol_mode = -1;
 
 	if (vol_id > ai->highest_vol_id)
 		ai->highest_vol_id = vol_id;
@@ -455,6 +456,7 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
 	uint32_t data_crc, crc;
 	struct ubi_vid_io_buf *vidb = NULL;
 	unsigned long long sqnum2 = be64_to_cpu(vid_hdr->sqnum);
+	enum ubi_io_mode io_mode;
 
 	if (sqnum2 == aeb->sqnum) {
 		/*
@@ -522,9 +524,10 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
 
 	len = be32_to_cpu(vid_hdr->data_size);
 
+	io_mode = ubi_io_mode_from_vid_hdr(vid_hdr);
+
 	mutex_lock(&ubi->buf_mutex);
-	err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, len,
-			       UBI_IO_MODE_NORMAL);
+	err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, len, io_mode);
 	if (err && err != UBI_IO_BITFLIPS && !mtd_is_eccerr(err))
 		goto out_unlock;
 
@@ -577,13 +580,14 @@ out_free_vidh:
 int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum,
 		  int ec, const struct ubi_vid_hdr *vid_hdr, int bitflips)
 {
-	int err, vol_id, lnum;
+	int err, vol_id, vol_mode, lnum;
 	unsigned long long sqnum;
 	struct ubi_ainf_volume *av;
 	struct ubi_ainf_peb *aeb;
 	struct rb_node **p, *parent = NULL;
 
 	vol_id = be32_to_cpu(vid_hdr->vol_id);
+	vol_mode = ubi_vol_mode_from_vid_hdr(vid_hdr);
 	lnum = be32_to_cpu(vid_hdr->lnum);
 	sqnum = be64_to_cpu(vid_hdr->sqnum);
 
@@ -594,6 +598,17 @@ int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum,
 	if (IS_ERR(av))
 		return PTR_ERR(av);
 
+	/* Assign the volume mode if it's just been created. */
+	if (av->vol_mode < 0)
+		av->vol_mode = vol_mode;
+
+	/* All VID headers in a given volume should expose the same mode. */
+	if (vol_mode != av->vol_mode) {
+		ubi_err(ubi, "invalid mode detected: got %d expected %d",
+			vid_hdr->vol_mode, av->vol_mode);
+		return -EINVAL;
+	}
+
 	if (ai->max_sqnum < sqnum)
 		ai->max_sqnum = sqnum;
 
diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
index 45c329694a5e..9dd36ffb9d1e 100644
--- a/drivers/mtd/ubi/cdev.c
+++ b/drivers/mtd/ubi/cdev.c
@@ -622,6 +622,15 @@ static int verify_mkvol_req(const struct ubi_device *ubi,
 	    req->vol_type != UBI_STATIC_VOLUME)
 		goto bad;
 
+	if (req->vol_mode != UBI_VOL_MODE_NORMAL &&
+	    req->vol_mode != UBI_VOL_MODE_SLC)
+		goto bad;
+
+	/* SLC mode is only supported by UBI version 2 and later. */
+	if (req->vol_mode == UBI_VOL_MODE_SLC &&
+	    ubi->version < 2)
+		goto bad;
+
 	if (req->alignment > ubi->leb_size)
 		goto bad;
 
diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c
index f101a4985a7c..c0c936b44928 100644
--- a/drivers/mtd/ubi/debug.c
+++ b/drivers/mtd/ubi/debug.c
@@ -85,6 +85,7 @@ void ubi_dump_vid_hdr(const struct ubi_vid_hdr *vid_hdr)
 	pr_err("\tmagic     %08x\n", be32_to_cpu(vid_hdr->magic));
 	pr_err("\tversion   %d\n",  (int)vid_hdr->version);
 	pr_err("\tvol_type  %d\n",  (int)vid_hdr->vol_type);
+	pr_err("\tvol_mode  %d\n",  (int)vid_hdr->vol_mode);
 	pr_err("\tcopy_flag %d\n",  (int)vid_hdr->copy_flag);
 	pr_err("\tcompat    %d\n",  (int)vid_hdr->compat);
 	pr_err("\tvol_id    %d\n",  be32_to_cpu(vid_hdr->vol_id));
@@ -112,6 +113,7 @@ void ubi_dump_vol_info(const struct ubi_volume *vol)
 	pr_err("\talignment       %d\n", vol->alignment);
 	pr_err("\tdata_pad        %d\n", vol->data_pad);
 	pr_err("\tvol_type        %d\n", vol->vol_type);
+	pr_err("\tvol_mode        %d\n", vol->vol_mode);
 	pr_err("\tname_len        %d\n", vol->name_len);
 	pr_err("\tusable_leb_size %d\n", vol->usable_leb_size);
 	pr_err("\tused_ebs        %d\n", vol->used_ebs);
@@ -144,6 +146,7 @@ void ubi_dump_vtbl_record(const struct ubi_vtbl_record *r, int idx)
 	pr_err("\talignment       %d\n", be32_to_cpu(r->alignment));
 	pr_err("\tdata_pad        %d\n", be32_to_cpu(r->data_pad));
 	pr_err("\tvol_type        %d\n", (int)r->vol_type);
+	pr_err("\tvol_mode        %d\n", (int)r->vol_mode);
 	pr_err("\tupd_marker      %d\n", (int)r->upd_marker);
 	pr_err("\tname_len        %d\n", name_len);
 
@@ -210,6 +213,7 @@ void ubi_dump_mkvol_req(const struct ubi_mkvol_req *req)
 	pr_err("\talignment %d\n",   req->alignment);
 	pr_err("\tbytes     %lld\n", (long long)req->bytes);
 	pr_err("\tvol_type  %d\n",   req->vol_type);
+	pr_err("\tvol_mode  %d\n",   req->vol_mode);
 	pr_err("\tname_len  %d\n",   req->name_len);
 
 	memcpy(nm, req->name, 16);
diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c
index 4424af7ea9ac..94128dc50fd0 100644
--- a/drivers/mtd/ubi/eba.c
+++ b/drivers/mtd/ubi/eba.c
@@ -516,6 +516,7 @@ int ubi_eba_read_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
 	struct ubi_vid_io_buf *vidb;
 	struct ubi_vid_hdr *vid_hdr;
 	uint32_t uninitialized_var(crc);
+	enum ubi_io_mode io_mode;
 
 	err = leb_read_lock(ubi, vol_id, lnum);
 	if (err)
@@ -601,8 +602,9 @@ retry:
 		ubi_free_vid_buf(vidb);
 	}
 
-	err = ubi_io_read_data(ubi, buf, pnum, offset, len,
-			       UBI_IO_MODE_NORMAL);
+	io_mode = ubi_vol_mode_to_io_mode(vol->vol_mode);
+
+	err = ubi_io_read_data(ubi, buf, pnum, offset, len, io_mode);
 	if (err) {
 		if (err == UBI_IO_BITFLIPS)
 			scrub = 1;
@@ -719,6 +721,7 @@ static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum,
 	struct ubi_device *ubi = vol->ubi;
 	struct ubi_vid_hdr *vid_hdr;
 	int new_pnum, err, vol_id = vol->vol_id, data_size;
+	enum ubi_io_mode io_mode;
 	uint32_t crc;
 
 	new_pnum = ubi_wl_get_peb(ubi);
@@ -739,13 +742,15 @@ static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum,
 
 	ubi_assert(vid_hdr->vol_type == UBI_VID_DYNAMIC);
 
+	io_mode = ubi_io_mode_from_vid_hdr(vid_hdr);
+
 	mutex_lock(&ubi->buf_mutex);
 	memset(ubi->peb_buf + offset, 0xFF, len);
 
 	/* Read everything before the area where the write failure happened */
 	if (offset > 0) {
 		err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset,
-				       UBI_IO_MODE_NORMAL);
+				       io_mode);
 		if (err && err != UBI_IO_BITFLIPS)
 			goto out_unlock;
 	}
@@ -763,7 +768,7 @@ static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum,
 		goto out_unlock;
 
 	err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size,
-				UBI_IO_MODE_NORMAL);
+				io_mode);
 
 out_unlock:
 	mutex_unlock(&ubi->buf_mutex);
@@ -848,8 +853,10 @@ static int try_write_vid_and_data(struct ubi_volume *vol, int lnum,
 				  struct ubi_vid_io_buf *vidb, const void *buf,
 				  int offset, int len)
 {
+	struct ubi_vid_hdr *vid_hdr = ubi_get_vid_hdr(vidb);
 	struct ubi_device *ubi = vol->ubi;
 	int pnum, opnum, err, vol_id = vol->vol_id;
+	enum ubi_io_mode io_mode;
 
 	pnum = ubi_wl_get_peb(ubi);
 	if (pnum < 0) {
@@ -862,6 +869,8 @@ static int try_write_vid_and_data(struct ubi_volume *vol, int lnum,
 	dbg_eba("write VID hdr and %d bytes at offset %d of LEB %d:%d, PEB %d",
 		len, offset, vol_id, lnum, pnum);
 
+	io_mode = ubi_io_mode_from_vid_hdr(vid_hdr);
+
 	err = ubi_io_write_vid_hdr(ubi, pnum, vidb);
 	if (err) {
 		ubi_warn(ubi, "failed to write VID header to LEB %d:%d, PEB %d",
@@ -871,7 +880,7 @@ static int try_write_vid_and_data(struct ubi_volume *vol, int lnum,
 
 	if (len) {
 		err = ubi_io_write_data(ubi, buf, pnum, offset, len,
-					UBI_IO_MODE_NORMAL);
+					io_mode);
 		if (err) {
 			ubi_warn(ubi,
 				 "failed to write %d bytes at offset %d of LEB %d:%d, PEB %d",
@@ -914,10 +923,13 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
 	int err, pnum, tries, vol_id = vol->vol_id;
 	struct ubi_vid_io_buf *vidb;
 	struct ubi_vid_hdr *vid_hdr;
+	enum ubi_io_mode io_mode;
 
 	if (ubi->ro_mode)
 		return -EROFS;
 
+	io_mode = ubi_vol_mode_to_io_mode(vol->vol_mode);
+
 	err = leb_write_lock(ubi, vol_id, lnum);
 	if (err)
 		return err;
@@ -927,8 +939,7 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
 		dbg_eba("write %d bytes at offset %d of LEB %d:%d, PEB %d",
 			len, offset, vol_id, lnum, pnum);
 
-		err = ubi_io_write_data(ubi, buf, pnum, offset, len,
-					UBI_IO_MODE_NORMAL);
+		err = ubi_io_write_data(ubi, buf, pnum, offset, len, io_mode);
 		if (err) {
 			ubi_warn(ubi, "failed to write data to PEB %d", pnum);
 			if (err == -EIO && ubi->bad_allowed)
@@ -952,6 +963,10 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
 	vid_hdr = ubi_get_vid_hdr(vidb);
 
 	vid_hdr->vol_type = UBI_VID_DYNAMIC;
+	if (vol->vol_mode == UBI_VOL_MODE_SLC)
+		vid_hdr->vol_mode = UBI_VID_MODE_SLC;
+	else
+		vid_hdr->vol_mode = UBI_VID_MODE_NORMAL;
 	vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi));
 	vid_hdr->vol_id = cpu_to_be32(vol_id);
 	vid_hdr->lnum = cpu_to_be32(lnum);
@@ -1041,6 +1056,11 @@ int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol,
 
 	crc = crc32(UBI_CRC32_INIT, buf, data_size);
 	vid_hdr->vol_type = UBI_VID_STATIC;
+	if (vol->vol_mode == UBI_VOL_MODE_SLC)
+		vid_hdr->vol_mode = UBI_VID_MODE_SLC;
+	else
+		vid_hdr->vol_mode = UBI_VID_MODE_NORMAL;
+
 	vid_hdr->data_size = cpu_to_be32(data_size);
 	vid_hdr->used_ebs = cpu_to_be32(used_ebs);
 	vid_hdr->data_crc = cpu_to_be32(crc);
@@ -1125,6 +1145,12 @@ int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol,
 
 	crc = crc32(UBI_CRC32_INIT, buf, len);
 	vid_hdr->vol_type = UBI_VID_DYNAMIC;
+
+	if (vol->vol_mode == UBI_VOL_MODE_SLC)
+		vid_hdr->vol_mode = UBI_VID_MODE_SLC;
+	else
+		vid_hdr->vol_mode = UBI_VID_MODE_NORMAL;
+
 	vid_hdr->data_size = cpu_to_be32(len);
 	vid_hdr->copy_flag = 1;
 	vid_hdr->data_crc = cpu_to_be32(crc);
@@ -1203,6 +1229,7 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 	int err, vol_id, lnum, data_size, aldata_size, idx;
 	struct ubi_vid_hdr *vid_hdr = ubi_get_vid_hdr(vidb);
 	struct ubi_volume *vol;
+	enum ubi_io_mode io_mode;
 	uint32_t crc;
 
 	vol_id = be32_to_cpu(vid_hdr->vol_id);
@@ -1266,6 +1293,8 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 		data_size = aldata_size =
 			    vol->leb_size - be32_to_cpu(vid_hdr->data_pad);
 
+	io_mode = ubi_vol_mode_to_io_mode(vol->vol_mode);
+
 	/*
 	 * OK, now the LEB is locked and we can safely start moving it. Since
 	 * this function utilizes the @ubi->peb_buf buffer which is shared
@@ -1275,7 +1304,7 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 	mutex_lock(&ubi->buf_mutex);
 	dbg_wl("read %d bytes of data", aldata_size);
 	err = ubi_io_read_data(ubi, ubi->peb_buf, from, 0, aldata_size,
-			       UBI_IO_MODE_NORMAL);
+			       io_mode);
 	if (err && err != UBI_IO_BITFLIPS) {
 		ubi_warn(ubi, "error %d while reading data from PEB %d",
 			 err, from);
@@ -1338,7 +1367,7 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
 
 	if (data_size > 0) {
 		err = ubi_io_write_data(ubi, ubi->peb_buf, to, 0, aldata_size,
-					UBI_IO_MODE_NORMAL);
+					io_mode);
 		if (err) {
 			if (err == -EIO)
 				err = MOVE_TARGET_WR_ERR;
diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index 329ad430473b..4a33b7a8d887 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -1058,6 +1058,40 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
 }
 
 /**
+ * validate_mode - validate a volume mode.
+ * @ubi: UBI device description object
+ * @vid_hdr: the volume identifier header to check
+ *
+ * This function checks that the volume mode stored in the volume identifier
+ * header @vid_hdr is correct. Returns zero if the VID header is OK and %1
+ * if not.
+ */
+static int validate_mode(const struct ubi_device *ubi,
+			 const struct ubi_vid_hdr *vid_hdr)
+{
+	/* All versions of UBI support normal mode. */
+	if (vid_hdr->vol_mode == UBI_VID_MODE_NORMAL)
+		return 0;
+
+	/* Version 1 only supports normal mode. */
+	if (vid_hdr->version == 1)
+		goto err;
+
+	/* Version 2 supports normal and SLC mode. */
+	if (vid_hdr->version == 2 &&
+	    vid_hdr->vol_mode != UBI_VID_MODE_SLC)
+		goto err;
+
+	return 0;
+
+err:
+	ubi_err(ubi, "mode %d not supported by UBI version %d",
+		vid_hdr->vol_mode, vid_hdr->version);
+
+	return -EINVAL;
+}
+
+/**
  * validate_vid_hdr - validate a volume identifier header.
  * @ubi: UBI device description object
  * @vid_hdr: the volume identifier header to check
@@ -1084,6 +1118,9 @@ static int validate_vid_hdr(const struct ubi_device *ubi,
 		goto bad;
 	}
 
+	if (validate_mode(ubi, vid_hdr))
+		goto bad;
+
 	if (copy_flag != 0 && copy_flag != 1) {
 		ubi_err(ubi, "bad copy_flag");
 		goto bad;
diff --git a/drivers/mtd/ubi/ubi-media.h b/drivers/mtd/ubi/ubi-media.h
index a536e482b954..94fa0d414db6 100644
--- a/drivers/mtd/ubi/ubi-media.h
+++ b/drivers/mtd/ubi/ubi-media.h
@@ -33,9 +33,10 @@
 #include <asm/byteorder.h>
 
 /* The version of UBI images supported by this implementation */
-#define UBI_CURRENT_VERSION		1
+#define UBI_CURRENT_VERSION		2
 #define UBI_SUPPORTS_VERSION(x)		BIT(x)
-#define UBI_SUPPORTED_VERSIONS		(UBI_SUPPORTS_VERSION(1))
+#define UBI_SUPPORTED_VERSIONS		(UBI_SUPPORTS_VERSION(1) |	\
+					 UBI_SUPPORTS_VERSION(2))
 #define UBI_VERSION_IS_SUPPORTED(x)	(BIT((x)) & UBI_SUPPORTED_VERSIONS)
 
 /* The highest erase counter value supported by this implementation */
@@ -115,6 +116,26 @@ enum {
 	UBI_COMPAT_REJECT   = 5
 };
 
+/*
+ * Mode constants used by internal volumes.
+ *
+ * @UBI_VID_MODE_NORMAL: eraseblocks are used as is. All pages within a block
+ *			 are written. Safe to be used on all devices except
+ *			 MLC/TLC NANDs
+ * @UBI_VID_MODE_SLC: eraseblocks are used in SLC mode (this is a software
+ *		      emulation of an SLC NAND, not the hardware SLC mode
+ *		      which is sometime provided by NAND vendors). Only the
+ *		      first write-unit/page of each pair of pages is used,
+ *		      which makes this mode robust against 'paired page'
+ *		      corruption.
+ *		      In the other hand, this means UBI will only expose half
+ *		      the capacity of the NAND.
+ */
+enum {
+	UBI_VID_MODE_NORMAL,
+	UBI_VID_MODE_SLC,
+};
+
 /* Sizes of UBI headers */
 #define UBI_EC_HDR_SIZE  sizeof(struct ubi_ec_hdr)
 #define UBI_VID_HDR_SIZE sizeof(struct ubi_vid_hdr)
@@ -182,6 +203,7 @@ struct ubi_ec_hdr {
  *          %UBI_COMPAT_IGNORE, %UBI_COMPAT_PRESERVE, or %UBI_COMPAT_REJECT)
  * @vol_id: ID of this volume
  * @lnum: logical eraseblock number
+ * @vol_mode: mode of this volume (%UBI_VID_MODE_NORMAL or %UBI_VID_MODE_SLC)
  * @padding1: reserved for future, zeroes
  * @data_size: how many bytes of data this logical eraseblock contains
  * @used_ebs: total number of used logical eraseblocks in this volume
@@ -287,7 +309,8 @@ struct ubi_vid_hdr {
 	__u8    compat;
 	__be32  vol_id;
 	__be32  lnum;
-	__u8    padding1[4];
+	__u8	vol_mode;
+	__u8    padding1[3];
 	__be32  data_size;
 	__be32  used_ebs;
 	__be32  data_pad;
@@ -339,6 +362,7 @@ struct ubi_vid_hdr {
  * @name_len: volume name length
  * @name: the volume name
  * @flags: volume flags (%UBI_VTBL_AUTORESIZE_FLG)
+ * @vol_mode: volume mode (%UBI_VID_MODE_NORMAL or %UBI_VID_MODE_SLC)
  * @padding: reserved, zeroes
  * @crc: a CRC32 checksum of the record
  *
@@ -375,7 +399,8 @@ struct ubi_vtbl_record {
 	__be16  name_len;
 	__u8    name[UBI_VOL_NAME_MAX+1];
 	__u8    flags;
-	__u8    padding[23];
+	__u8	vol_mode;
+	__u8    padding[22];
 	__be32  crc;
 } __packed;
 
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index de4c65cb8f7a..e667bf4f4b67 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -324,6 +324,7 @@ struct ubi_eba_leb_desc {
  *
  * @reserved_pebs: how many physical eraseblocks are reserved for this volume
  * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME)
+ * @vol_mode: volume mode (%UBI_VOL_MODE_NORMAL or %UBI_VOL_MODE_SLC)
  * @leb_size: logical eraseblock size
  * @usable_leb_size: logical eraseblock size without padding
  * @used_ebs: how many logical eraseblocks in this volume contain data
@@ -374,6 +375,7 @@ struct ubi_volume {
 
 	int reserved_pebs;
 	int vol_type;
+	int vol_mode;
 	int leb_size;
 	int usable_leb_size;
 	int used_ebs;
@@ -718,6 +720,7 @@ struct ubi_ainf_peb {
  * @highest_lnum: highest logical eraseblock number in this volume
  * @leb_count: number of logical eraseblocks in this volume
  * @vol_type: volume type
+ * @vol_mode: volume mode (%UBI_VOL_MODE_NORMAL or %UBI_VOL_MODE_SLC)
  * @used_ebs: number of used logical eraseblocks in this volume (only for
  *            static volumes)
  * @last_data_size: amount of data in the last logical eraseblock of this
@@ -726,6 +729,7 @@ struct ubi_ainf_peb {
  * @data_pad: how many bytes at the end of logical eraseblocks of this volume
  *            are not used (due to volume alignment)
  * @compat: compatibility flags of this volume
+ * @mode: volume mode (see %UBI_NORMAL_MODE and %UBI_NORMAL_MODE)
  * @rb: link in the volume RB-tree
  * @root: root of the RB-tree containing all the eraseblock belonging to this
  *        volume (&struct ubi_ainf_peb objects)
@@ -738,6 +742,7 @@ struct ubi_ainf_volume {
 	int highest_lnum;
 	int leb_count;
 	int vol_type;
+	int vol_mode;
 	int used_ebs;
 	int last_data_size;
 	int data_pad;
@@ -1172,6 +1177,61 @@ static inline int ubi_io_write_data(struct ubi_device *ubi, const void *buf,
 }
 
 /**
+ * ubi_io_mode_from_vid_hdr - Extract the I/O mode from VID header information.
+ * @vid_hdr: VID header to extract information from
+ */
+static inline int ubi_io_mode_from_vid_hdr(const struct ubi_vid_hdr *vid_hdr)
+{
+	switch (vid_hdr->vol_mode) {
+	case UBI_VID_MODE_NORMAL:
+		return UBI_IO_MODE_NORMAL;
+	case UBI_VID_MODE_SLC:
+		return UBI_IO_MODE_SLC;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * ubi_vol_mode_from_vid_hdr - Extract the volume mode from VID header
+ *			       information.
+ * @vid_hdr: VID header to extract information from
+ */
+static inline int ubi_vol_mode_from_vid_hdr(const struct ubi_vid_hdr *vid_hdr)
+{
+	switch (vid_hdr->vol_mode) {
+	case UBI_VID_MODE_NORMAL:
+		return UBI_VOL_MODE_NORMAL;
+	case UBI_VID_MODE_SLC:
+		return UBI_VOL_MODE_SLC;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+/**
+ * ubi_vol_mode_to_io_mode - Convert volume mode into I/O mode.
+ * @vol_mode: volume mode (%UBI_VOL_MODE_NORMAL or %UBI_VOL_MODE_SLC)
+ */
+static inline int ubi_vol_mode_to_io_mode(int vol_mode)
+{
+	switch (vol_mode) {
+	case UBI_VOL_MODE_NORMAL:
+		return UBI_IO_MODE_NORMAL;
+	case UBI_VOL_MODE_SLC:
+		return UBI_IO_MODE_SLC;
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+/**
  * ubi_ro_mode - switch to read-only mode.
  * @ubi: UBI device description object
  */
diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c
index ac703d4631e1..b85f0ab4a2b3 100644
--- a/drivers/mtd/ubi/vmt.c
+++ b/drivers/mtd/ubi/vmt.c
@@ -39,6 +39,8 @@ static struct device_attribute attr_vol_reserved_ebs =
 	__ATTR(reserved_ebs, S_IRUGO, vol_attribute_show, NULL);
 static struct device_attribute attr_vol_type =
 	__ATTR(type, S_IRUGO, vol_attribute_show, NULL);
+static struct device_attribute attr_vol_mode =
+	__ATTR(mode, S_IRUGO, vol_attribute_show, NULL);
 static struct device_attribute attr_vol_name =
 	__ATTR(name, S_IRUGO, vol_attribute_show, NULL);
 static struct device_attribute attr_vol_corrupted =
@@ -95,6 +97,14 @@ static ssize_t vol_attribute_show(struct device *dev,
 		else
 			tp = "static";
 		ret = sprintf(buf, "%s\n", tp);
+	} else if (attr == &attr_vol_mode) {
+		const char *mode;
+
+		if (vol->vol_mode == UBI_VOL_MODE_SLC)
+			mode = "slc";
+		else
+			mode = "normal";
+		ret = sprintf(buf, "%s\n", mode);
 	} else if (attr == &attr_vol_name)
 		ret = sprintf(buf, "%s\n", vol->name);
 	else if (attr == &attr_vol_corrupted)
@@ -123,6 +133,7 @@ static ssize_t vol_attribute_show(struct device *dev,
 static struct attribute *volume_dev_attrs[] = {
 	&attr_vol_reserved_ebs.attr,
 	&attr_vol_type.attr,
+	&attr_vol_mode.attr,
 	&attr_vol_name.attr,
 	&attr_vol_corrupted.attr,
 	&attr_vol_alignment.attr,
@@ -186,9 +197,9 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 		req->vol_id = vol_id;
 	}
 
-	dbg_gen("create device %d, volume %d, %llu bytes, type %d, name %s",
+	dbg_gen("create device %d, volume %d, %llu bytes, type %d, mode %d, name %s",
 		ubi->ubi_num, vol_id, (unsigned long long)req->bytes,
-		(int)req->vol_type, req->name);
+		(int)req->vol_type, (int)req->vol_mode, req->name);
 
 	/* Ensure that this volume does not exist */
 	err = -EEXIST;
@@ -207,12 +218,12 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 			goto out_unlock;
 		}
 
-	/*
-	 * Volume LEB size is currently PEB size - (size reserved for the EC
-	 * and VID headers). This will change with MLC/TLC NAND support and
-	 * the LEB consolidation concept.
-	 */
-	vol->leb_size = ubi->leb_size;
+	/* LEB size is adapted when SLC mode is requested. */
+	if (req->vol_mode == UBI_VOL_MODE_SLC)
+		vol->leb_size = (ubi->peb_size / ubi->max_lebs_per_peb) -
+				ubi->leb_start;
+	else
+		vol->leb_size = ubi->leb_size;
 
 	/* Calculate how many eraseblocks are requested */
 	vol->alignment = req->alignment;
@@ -237,6 +248,7 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 
 	vol->vol_id    = vol_id;
 	vol->vol_type  = req->vol_type;
+	vol->vol_mode  = req->vol_mode;
 	vol->name_len  = req->name_len;
 	memcpy(vol->name, req->name, vol->name_len);
 	vol->ubi = ubi;
@@ -305,6 +317,10 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
 		vtbl_rec.vol_type = UBI_VID_DYNAMIC;
 	else
 		vtbl_rec.vol_type = UBI_VID_STATIC;
+	if (vol->vol_mode == UBI_VOL_MODE_SLC)
+		vtbl_rec.vol_mode = UBI_VID_MODE_SLC;
+	else
+		vtbl_rec.vol_mode = UBI_VID_MODE_NORMAL;
 	memcpy(vtbl_rec.name, vol->name, vol->name_len);
 
 	err = ubi_change_vtbl_record(ubi, vol_id, &vtbl_rec);
diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c
index 8f4abecdae59..ca5e45bc0ce1 100644
--- a/drivers/mtd/ubi/vtbl.c
+++ b/drivers/mtd/ubi/vtbl.c
@@ -171,7 +171,7 @@ static int vtbl_check(const struct ubi_device *ubi,
 		      const struct ubi_vtbl_record *vtbl)
 {
 	int i, n, reserved_pebs, alignment, data_pad, vol_type, name_len;
-	int upd_marker, err;
+	int vol_mode, upd_marker, err;
 	uint32_t crc;
 	const char *name;
 
@@ -183,6 +183,7 @@ static int vtbl_check(const struct ubi_device *ubi,
 		data_pad = be32_to_cpu(vtbl[i].data_pad);
 		upd_marker = vtbl[i].upd_marker;
 		vol_type = vtbl[i].vol_type;
+		vol_mode = vtbl[i].vol_mode;
 		name_len = be16_to_cpu(vtbl[i].name_len);
 		name = &vtbl[i].name[0];
 
@@ -258,6 +259,12 @@ static int vtbl_check(const struct ubi_device *ubi,
 			err = 12;
 			goto bad;
 		}
+
+		if (vol_mode != UBI_VID_MODE_NORMAL &&
+		    vol_mode != UBI_VID_MODE_SLC) {
+			err = 13;
+			goto bad;
+		}
 	}
 
 	/* Checks that all names are unique */
@@ -302,6 +309,7 @@ static int create_vtbl(struct ubi_device *ubi, struct ubi_attach_info *ai,
 	struct ubi_vid_io_buf *vidb;
 	struct ubi_vid_hdr *vid_hdr;
 	struct ubi_ainf_peb *new_aeb;
+	enum ubi_io_mode io_mode;
 
 	dbg_gen("create volume table (copy #%d)", copy + 1);
 
@@ -318,6 +326,14 @@ retry:
 		goto out_free;
 	}
 
+	if (ubi->version > 1 && ubi->max_lebs_per_peb) {
+		vid_hdr->vol_mode = UBI_VID_MODE_SLC;
+		io_mode = UBI_IO_MODE_SLC;
+	} else {
+		vid_hdr->vol_mode = UBI_VID_MODE_NORMAL;
+		io_mode = UBI_IO_MODE_NORMAL;
+	}
+
 	vid_hdr->vol_type = UBI_LAYOUT_VOLUME_TYPE;
 	vid_hdr->vol_id = cpu_to_be32(UBI_LAYOUT_VOLUME_ID);
 	vid_hdr->compat = UBI_LAYOUT_VOLUME_COMPAT;
@@ -333,7 +349,7 @@ retry:
 
 	/* Write the layout volume contents */
 	err = ubi_io_write_data(ubi, vtbl, new_aeb->pnum, 0, ubi->vtbl_size,
-				UBI_IO_MODE_NORMAL);
+				io_mode);
 	if (err)
 		goto write_error;
 
@@ -381,6 +397,7 @@ static struct ubi_vtbl_record *process_lvol(struct ubi_device *ubi,
 	struct ubi_ainf_peb *aeb;
 	struct ubi_vtbl_record *leb[UBI_LAYOUT_VOLUME_EBS] = { NULL, NULL };
 	int leb_corrupted[UBI_LAYOUT_VOLUME_EBS] = {1, 1};
+	enum ubi_io_mode io_mode;
 
 	/*
 	 * UBI goes through the following steps when it changes the layout
@@ -407,6 +424,8 @@ static struct ubi_vtbl_record *process_lvol(struct ubi_device *ubi,
 	 *    to LEB 0.
 	 */
 
+	io_mode = ubi_vol_mode_to_io_mode(av->vol_mode);
+
 	dbg_gen("check layout volume");
 
 	/* Read both LEB 0 and LEB 1 into memory */
@@ -418,7 +437,7 @@ static struct ubi_vtbl_record *process_lvol(struct ubi_device *ubi,
 		}
 
 		err = ubi_io_read_data(ubi, leb[aeb->lnum], aeb->pnum,
-				       0, ubi->vtbl_size, UBI_IO_MODE_NORMAL);
+				       0, ubi->vtbl_size, io_mode);
 		if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err))
 			/*
 			 * Scrub the PEB later. Note, -EBADMSG indicates an
@@ -555,8 +574,16 @@ static int init_volumes(struct ubi_device *ubi,
 		vol->upd_marker = vtbl[i].upd_marker;
 		vol->vol_type = vtbl[i].vol_type == UBI_VID_DYNAMIC ?
 					UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME;
+		if (vtbl[i].vol_mode == UBI_VID_MODE_SLC) {
+			vol->vol_mode = UBI_VOL_MODE_SLC;
+			vol->leb_size = (ubi->peb_size /
+					 ubi->max_lebs_per_peb) -
+					ubi->leb_start;
+		} else {
+			vol->vol_mode = UBI_VOL_MODE_NORMAL;
+			vol->leb_size = ubi->leb_size;
+		}
 		vol->name_len = be16_to_cpu(vtbl[i].name_len);
-		vol->leb_size = ubi->leb_size;
 		vol->usable_leb_size = vol->leb_size - vol->data_pad;
 		memcpy(vol->name, vtbl[i].name, vol->name_len);
 		vol->name[vol->name_len] = '\0';
@@ -629,14 +656,22 @@ static int init_volumes(struct ubi_device *ubi,
 	if (!vol)
 		return -ENOMEM;
 
+	av = ubi_find_av(ai, UBI_LAYOUT_VOLUME_ID);
+	ubi_assert(av);
+
 	vol->reserved_pebs = UBI_LAYOUT_VOLUME_EBS;
 	vol->alignment = UBI_LAYOUT_VOLUME_ALIGN;
 	vol->vol_type = UBI_DYNAMIC_VOLUME;
+	vol->vol_mode = av->vol_mode;
+	if (vol->vol_mode == UBI_VOL_MODE_SLC)
+		vol->leb_size = (ubi->peb_size / ubi->max_lebs_per_peb) -
+				ubi->leb_start;
+	else
+		vol->leb_size = ubi->leb_size;
 	vol->name_len = sizeof(UBI_LAYOUT_VOLUME_NAME) - 1;
 	memcpy(vol->name, UBI_LAYOUT_VOLUME_NAME, vol->name_len + 1);
 	vol->used_ebs = vol->reserved_pebs;
 	vol->last_eb_bytes = vol->reserved_pebs;
-	vol->leb_size = ubi->leb_size;
 	vol->usable_leb_size = vol->leb_size;
 	vol->used_bytes =
 		(long long)vol->used_ebs * (vol->leb_size - vol->data_pad);
diff --git a/include/uapi/mtd/ubi-user.h b/include/uapi/mtd/ubi-user.h
index 1927b0d78a99..2acf4a753c65 100644
--- a/include/uapi/mtd/ubi-user.h
+++ b/include/uapi/mtd/ubi-user.h
@@ -224,6 +224,26 @@ enum {
 };
 
 /*
+ * UBI volume mode constants.
+ *
+ * @UBI_VOL_MODE_NORMAL: eraseblocks are used as is. All pages within a block
+ *			 are written. Safe to be used on all devices except
+ *			 MLC/TLC NANDs
+ * @UBI_VOL_MODE_SLC: eraseblocks are used in SLC mode (this is a software
+ *		      emulation of an SLC NAND, not the hardware SLC mode
+ *		      which is sometime provided by NAND vendors). Only the
+ *		      first write-unit/page of each pair of pages is used,
+ *		      which makes this mode robust against 'paired page'
+ *		      corruption.
+ *		      In the other hand, this means UBI will only expose half
+ *		      the capacity of the NAND.
+ */
+enum {
+	UBI_VOL_MODE_NORMAL,
+	UBI_VOL_MODE_SLC,
+};
+
+/*
  * UBI set volume property ioctl constants.
  *
  * @UBI_VOL_PROP_DIRECT_WRITE: allow (any non-zero value) or disallow (value 0)
@@ -291,7 +311,7 @@ struct ubi_attach_req {
  * @alignment: volume alignment
  * @bytes: volume size in bytes
  * @vol_type: volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME)
- * @padding1: reserved for future, not used, has to be zeroed
+ * @vol_mode: volume mode (%UBI_VOL_MODE_NORMAL or %UBI_VOL_MODE_SLC)
  * @name_len: volume name length
  * @padding2: reserved for future, not used, has to be zeroed
  * @name: volume name
@@ -320,7 +340,7 @@ struct ubi_mkvol_req {
 	__s32 alignment;
 	__s64 bytes;
 	__s8 vol_type;
-	__s8 padding1;
+	__s8 vol_mode;
 	__s16 name_len;
 	__s8 padding2[4];
 	char name[UBI_MAX_VOLUME_NAME + 1];
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad)
  2016-09-05 15:31 ` [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad) Boris Brezillon
@ 2016-09-05 17:42   ` Boris Brezillon
  0 siblings, 0 replies; 8+ messages in thread
From: Boris Brezillon @ 2016-09-05 17:42 UTC (permalink / raw)
  To: Artem Bityutskiy, Richard Weinberger
  Cc: David Woodhouse, Brian Norris, linux-mtd, linux-kernel

On Mon,  5 Sep 2016 17:31:24 +0200
Boris Brezillon <boris.brezillon@free-electrons.com> wrote:

> vol->usable_size is already set to ubi->leb_size - vol->data_pad. Use
> vol->usable_size instead of recalculating it.

This one should be part of the cleanup series.

> 
> Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
> ---
>  drivers/mtd/ubi/cdev.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
> index 72ba84af6698..45c329694a5e 100644
> --- a/drivers/mtd/ubi/cdev.c
> +++ b/drivers/mtd/ubi/cdev.c
> @@ -416,7 +416,7 @@ static long vol_cdev_ioctl(struct file *file, unsigned int cmd,
>  		}
>  
>  		rsvd_bytes = (long long)vol->reserved_pebs *
> -					ubi->leb_size-vol->data_pad;
> +					vol->usable_leb_size;
>  		if (bytes < 0 || bytes > rsvd_bytes) {
>  			err = -EINVAL;
>  			break;

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2016-09-05 17:42 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-05 15:31 [PATCH 0/6] UBI: add support for 'SLC mode' volumes Boris Brezillon
2016-09-05 15:31 ` [PATCH 1/6] UBI: dissociate the on-flash format version and the userspace ABI version Boris Brezillon
2016-09-05 15:31 ` [PATCH 2/6] UBI: rework the on-flash format versioning logic Boris Brezillon
2016-09-05 15:31 ` [PATCH 3/6] UBI: use vol->usable_leb_size instead of (ubi->leb_size - vol->data_pad) Boris Brezillon
2016-09-05 17:42   ` Boris Brezillon
2016-09-05 15:31 ` [PATCH 4/6] UBI: introduce per-volume leb_size Boris Brezillon
2016-09-05 15:31 ` [PATCH 5/6] UBI: io: support SLC mode accesses Boris Brezillon
2016-09-05 15:31 ` [PATCH 6/6] UBI: add SLC mode support Boris Brezillon

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.