|
@@ -400,72 +400,153 @@ erase_err:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs,
|
|
|
+ uint64_t *len)
|
|
|
+{
|
|
|
+ struct mtd_info *mtd = &nor->mtd;
|
|
|
+ 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 spi_nor *nor, loff_t ofs, uint64_t len,
|
|
|
+ u8 sr)
|
|
|
+{
|
|
|
+ loff_t lock_offs;
|
|
|
+ uint64_t lock_len;
|
|
|
+
|
|
|
+ stm_get_locked_range(nor, sr, &lock_offs, &lock_len);
|
|
|
+
|
|
|
+ return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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 spi_nor *nor, loff_t ofs, uint64_t len)
|
|
|
{
|
|
|
struct mtd_info *mtd = &nor->mtd;
|
|
|
- uint32_t offset = ofs;
|
|
|
- uint8_t status_old, status_new;
|
|
|
- int ret = 0;
|
|
|
+ u8 status_old, status_new;
|
|
|
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
+ u8 shift = ffs(mask) - 1, pow, val;
|
|
|
|
|
|
status_old = read_sr(nor);
|
|
|
|
|
|
- if (offset < mtd->size - (mtd->size / 2))
|
|
|
- status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
- else if (offset < mtd->size - (mtd->size / 4))
|
|
|
- status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
|
|
|
- else if (offset < mtd->size - (mtd->size / 8))
|
|
|
- status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
|
|
|
- else if (offset < mtd->size - (mtd->size / 16))
|
|
|
- status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2;
|
|
|
- else if (offset < mtd->size - (mtd->size / 32))
|
|
|
- status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
|
|
|
- else if (offset < mtd->size - (mtd->size / 64))
|
|
|
- status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1;
|
|
|
- else
|
|
|
- status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0;
|
|
|
+ /* SPI NOR always locks to the end */
|
|
|
+ if (ofs + len != mtd->size) {
|
|
|
+ /* Does combined region extend to end? */
|
|
|
+ if (!stm_is_locked_sr(nor, 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 & (SR_BP2 | SR_BP1 | SR_BP0)) >
|
|
|
- (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) {
|
|
|
- write_enable(nor);
|
|
|
- ret = write_sr(nor, status_new);
|
|
|
- }
|
|
|
+ if ((status_new & mask) <= (status_old & mask))
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
- return ret;
|
|
|
+ write_enable(nor);
|
|
|
+ return write_sr(nor, status_new);
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Unlock a region of the flash. See stm_lock() for more info
|
|
|
+ *
|
|
|
+ * Returns negative on errors, 0 on success.
|
|
|
+ */
|
|
|
static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
|
|
{
|
|
|
struct mtd_info *mtd = &nor->mtd;
|
|
|
- uint32_t offset = ofs;
|
|
|
uint8_t status_old, status_new;
|
|
|
- int ret = 0;
|
|
|
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
+ u8 shift = ffs(mask) - 1, pow, val;
|
|
|
|
|
|
status_old = read_sr(nor);
|
|
|
|
|
|
- if (offset+len > mtd->size - (mtd->size / 64))
|
|
|
- status_new = status_old & ~(SR_BP2 | SR_BP1 | SR_BP0);
|
|
|
- else if (offset+len > mtd->size - (mtd->size / 32))
|
|
|
- status_new = (status_old & ~(SR_BP2 | SR_BP1)) | SR_BP0;
|
|
|
- else if (offset+len > mtd->size - (mtd->size / 16))
|
|
|
- status_new = (status_old & ~(SR_BP2 | SR_BP0)) | SR_BP1;
|
|
|
- else if (offset+len > mtd->size - (mtd->size / 8))
|
|
|
- status_new = (status_old & ~SR_BP2) | SR_BP1 | SR_BP0;
|
|
|
- else if (offset+len > mtd->size - (mtd->size / 4))
|
|
|
- status_new = (status_old & ~(SR_BP0 | SR_BP1)) | SR_BP2;
|
|
|
- else if (offset+len > mtd->size - (mtd->size / 2))
|
|
|
- status_new = (status_old & ~SR_BP1) | SR_BP2 | SR_BP0;
|
|
|
- else
|
|
|
- status_new = (status_old & ~SR_BP0) | SR_BP2 | SR_BP1;
|
|
|
+ /* Cannot unlock; would unlock larger region than requested */
|
|
|
+ if (stm_is_locked_sr(nor, status_old, ofs - mtd->erasesize,
|
|
|
+ mtd->erasesize))
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
- /* Only modify protection if it will not lock other areas */
|
|
|
- if ((status_new & (SR_BP2 | SR_BP1 | SR_BP0)) <
|
|
|
- (status_old & (SR_BP2 | SR_BP1 | SR_BP0))) {
|
|
|
- write_enable(nor);
|
|
|
- ret = write_sr(nor, status_new);
|
|
|
+ /*
|
|
|
+ * 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;
|
|
|
}
|
|
|
|
|
|
- return ret;
|
|
|
+ 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_enable(nor);
|
|
|
+ return write_sr(nor, status_new);
|
|
|
}
|
|
|
|
|
|
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|