[U-Boot] [PATCH v10 05/27] mtd: spi-nor: sync/modify lock operations

Jagan Teki jagan at amarulasolutions.com
Thu Dec 28 06:12:11 UTC 2017


sync the lock operations from legacy spi_flash.c and
update them according to current spi-nor framework.

Signed-off-by: Suneel Garapati <suneelglinux at gmail.com>
Signed-off-by: Jagan Teki <jagan at amarulasolutions.com>
---
 drivers/mtd/spi-nor/spi-nor.c | 218 ++++++++++++++++++++++++++++++++++++++++++
 include/mtd.h                 |   3 +
 2 files changed, 221 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index cfd21fb..8bf9e67 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -186,6 +186,7 @@ static const struct spi_nor_info *spi_nor_id(struct udevice *dev)
 int spi_nor_merase(struct udevice *dev, struct erase_info *instr)
 {
 	struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+	const struct mtd_ops *mops = mtd_get_ops(mtd->dev);
 	int devnum = mtd->devnum;
 	struct spi_nor *nor;
 	const struct spi_nor_ops *ops;
@@ -205,6 +206,14 @@ int spi_nor_merase(struct udevice *dev, struct erase_info *instr)
 	addr = instr->addr;
 	len = instr->len;
 
+	if (mops->is_locked) {
+		ret = mops->is_locked(mtd->dev, addr, len);
+		if (ret > 0) {
+			printf("spi-nor: offset 0x%x is locked, cannot be erased\n", addr);
+			return -EINVAL;
+		}
+	}
+
 	while (len) {
 		erase_addr = addr;
 
@@ -238,6 +247,7 @@ static int spi_nor_mwrite(struct udevice *dev, loff_t to, size_t len,
 			  size_t *retlen, const u_char *buf)
 {
 	struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+	const struct mtd_ops *mops = mtd_get_ops(mtd->dev);
 	int devnum = mtd->devnum;
 	struct spi_nor *nor;
 	const struct spi_nor_ops *ops;
@@ -253,6 +263,14 @@ static int spi_nor_mwrite(struct udevice *dev, loff_t to, size_t len,
 
 	page_size = mtd->writebufsize;
 
+	if (mops->is_locked) {
+		ret = mops->is_locked(mtd->dev, to, len);
+		if (ret > 0) {
+			printf("spi-nor: offset 0x%llx is locked, cannot be written\n", to);
+			return -EINVAL;
+		}
+	}
+
 	for (actual = 0; actual < len; actual += chunk_len) {
 		addr = to;
 
@@ -423,6 +441,197 @@ static int sst_mwrite_bp(struct udevice *dev, loff_t to, size_t len,
 }
 #endif
 
+#if defined(CONFIG_SPI_NOR_STMICRO) || defined(CONFIG_SPI_NOR_SST)
+static void stm_get_locked_range(struct mtd_info *mtd, u8 sr, loff_t *ofs,
+				 u64 *len)
+{
+	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+	int shift = ffs(mask) - 1;
+	int pow;
+
+	if (!(sr & mask)) {
+		/* No protection */
+		*ofs = 0;
+		*len = 0;
+	} else {
+		pow = ((sr & mask) ^ mask) >> shift;
+		*len = mtd->size >> pow;
+		*ofs = mtd->size - *len;
+	}
+}
+
+/* Return 1 if the entire region is locked, 0 otherwise */
+static int stm_is_locked_sr(struct mtd_info *mtd, loff_t ofs, u64 len,
+			    u8 sr)
+{
+	loff_t lock_offs;
+	u64 lock_len;
+
+	stm_get_locked_range(mtd, sr, &lock_offs, &lock_len);
+
+	return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
+}
+
+/*
+ * Check if a region of the flash is (completely) locked. See stm_lock() for
+ * more info.
+ *
+ * Returns 1 if entire region is locked, 0 if any portion is unlocked, and
+ * negative on errors.
+ */
+static int stm_is_locked(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+	struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+	int devnum = mtd->devnum;
+	struct spi_nor *nor;
+	int status;
+
+	nor = find_spi_nor_device(devnum);
+	if (!nor)
+		return -ENODEV;
+
+	status = read_sr(nor->dev);
+	if (status < 0)
+		return status;
+
+	return stm_is_locked_sr(mtd, ofs, len, status);
+}
+
+/*
+ * Lock a region of the flash. Compatible with ST Micro and similar flash.
+ * Supports only the block protection bits BP{0,1,2} in the status register
+ * (SR). Does not support these features found in newer SR bitfields:
+ *   - TB: top/bottom protect - only handle TB=0 (top protect)
+ *   - SEC: sector/block protect - only handle SEC=0 (block protect)
+ *   - CMP: complement protect - only support CMP=0 (range is not complemented)
+ *
+ * Sample table portion for 8MB flash (Winbond w25q64fw):
+ *
+ *   SEC  |  TB   |  BP2  |  BP1  |  BP0  |  Prot Length  | Protected Portion
+ *  --------------------------------------------------------------------------
+ *    X   |   X   |   0   |   0   |   0   |  NONE         | NONE
+ *    0   |   0   |   0   |   0   |   1   |  128 KB       | Upper 1/64
+ *    0   |   0   |   0   |   1   |   0   |  256 KB       | Upper 1/32
+ *    0   |   0   |   0   |   1   |   1   |  512 KB       | Upper 1/16
+ *    0   |   0   |   1   |   0   |   0   |  1 MB         | Upper 1/8
+ *    0   |   0   |   1   |   0   |   1   |  2 MB         | Upper 1/4
+ *    0   |   0   |   1   |   1   |   0   |  4 MB         | Upper 1/2
+ *    X   |   X   |   1   |   1   |   1   |  8 MB         | ALL
+ *
+ * Returns negative on errors, 0 on success.
+ */
+static int stm_lock(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+	struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+	int devnum = mtd->devnum;
+	struct spi_nor *nor;
+	u8 status_old, status_new;
+	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+	u8 shift = ffs(mask) - 1, pow, val;
+
+	nor = find_spi_nor_device(devnum);
+	if (!nor)
+		return -ENODEV;
+
+	status_old = read_sr(nor->dev);
+	if (status_old < 0)
+		return status_old;
+
+	/* SPI NOR always locks to the end */
+	if (ofs + len != mtd->size) {
+		/* Does combined region extend to end? */
+		if (!stm_is_locked_sr(mtd, ofs + len, mtd->size - ofs - len,
+				      status_old))
+			return -EINVAL;
+		len = mtd->size - ofs;
+	}
+
+	/*
+	 * Need smallest pow such that:
+	 *
+	 *   1 / (2^pow) <= (len / size)
+	 *
+	 * so (assuming power-of-2 size) we do:
+	 *
+	 *   pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
+	 */
+	pow = ilog2(mtd->size) - ilog2(len);
+	val = mask - (pow << shift);
+	if (val & ~mask)
+		return -EINVAL;
+
+	/* Don't "lock" with no region! */
+	if (!(val & mask))
+		return -EINVAL;
+
+	status_new = (status_old & ~mask) | val;
+
+	/* Only modify protection if it will not unlock other areas */
+	if ((status_new & mask) <= (status_old & mask))
+		return -EINVAL;
+
+	write_sr(nor->dev, status_new);
+
+	return 0;
+}
+
+/*
+ * Unlock a region of the flash. See stm_lock() for more info
+ *
+ * Returns negative on errors, 0 on success.
+ */
+static int stm_unlock(struct udevice *dev, loff_t ofs, uint64_t len)
+{
+	struct mtd_info *mtd = dev_get_uclass_platdata(dev);
+	int devnum = mtd->devnum;
+	struct spi_nor *nor;
+	uint8_t status_old, status_new;
+	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+	u8 shift = ffs(mask) - 1, pow, val;
+
+	nor = find_spi_nor_device(devnum);
+	if (!nor)
+		return -ENODEV;
+
+	status_old = read_sr(nor->dev);
+	if (status_old < 0)
+		return status_old;
+
+	/* Cannot unlock; would unlock larger region than requested */
+	if (stm_is_locked_sr(mtd, ofs - mtd->erasesize, mtd->erasesize,
+			     status_old))
+		return -EINVAL;
+	/*
+	 * Need largest pow such that:
+	 *
+	 *   1 / (2^pow) >= (len / size)
+	 *
+	 * so (assuming power-of-2 size) we do:
+	 *
+	 *   pow = floor(log2(size / len)) = log2(size) - ceil(log2(len))
+	 */
+	pow = ilog2(mtd->size) - order_base_2(mtd->size - (ofs + len));
+	if (ofs + len == mtd->size) {
+		val = 0; /* fully unlocked */
+	} else {
+		val = mask - (pow << shift);
+		/* Some power-of-two sizes are not supported */
+		if (val & ~mask)
+			return -EINVAL;
+	}
+
+	status_new = (status_old & ~mask) | val;
+
+	/* Only modify protection if it will not lock other areas */
+	if ((status_new & mask) >= (status_old & mask))
+		return -EINVAL;
+
+	write_sr(nor->dev, status_new);
+
+	return 0;
+}
+#endif
+
 #ifdef CONFIG_SPI_NOR_MACRONIX
 static int macronix_quad_enable(struct udevice *dev)
 {
@@ -598,6 +807,15 @@ int spi_nor_scan(struct spi_nor *nor)
 	}
 #endif
 
+#if defined(CONFIG_SPI_NOR_STMICRO) || defined(CONFIG_SPI_NOR_SST)
+	/* NOR protection support for STmicro/Micron chips and similar */
+	if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
+	    JEDEC_MFR(info) == SNOR_MFR_SST) {
+		ops->lock = stm_lock;
+		ops->unlock = stm_unlock;
+		ops->is_locked = stm_is_locked;
+	}
+#endif
 	/* compute the flash size */
 	nor->page_size = info->page_size;
 	/*
diff --git a/include/mtd.h b/include/mtd.h
index 273b3a6..acb2256 100644
--- a/include/mtd.h
+++ b/include/mtd.h
@@ -26,6 +26,9 @@ struct mtd_ops {
 		     size_t *retlen, u_char *buf);
 	int (*write)(struct udevice *dev, loff_t to, size_t len,
 		      size_t *retlen, const u_char *buf);
+	int (*lock) (struct udevice *dev, loff_t ofs, uint64_t len);
+	int (*unlock) (struct udevice *dev, loff_t ofs, uint64_t len);
+	int (*is_locked) (struct udevice *dev, loff_t ofs, uint64_t len);
 };
 
 /* Access the serial operations for a device */
-- 
2.7.4



More information about the U-Boot mailing list