@@ -3068,6 +3068,18 @@ CBFS (Coreboot Filesystem) support
memories can be connected with a given cs line.
Currently Xilinx Zynq qspi supports these type of connections.
+ CONFIG_SF_STM_PROTECT
+
+ Enable the built-in protection mechanism provided by the
+ BP2, BP1 and BP0 bits from the status register present
+ on ST-Micro flashes such as M25P32. Please refer to the
+ M25P32 datasheet to understand how to program these bits
+ in order to protect a selected region of the SPI NOR flash.
+ This same bit protection programming model applies to SPI
+ NOR flashes from other manufacturers such as:
+ - Micron M25P32
+ - SST SST25V32B
+
- SystemACE Support:
CONFIG_SYSTEMACE
@@ -348,6 +348,28 @@ static int do_spi_flash_erase(int argc, char * const argv[])
return ret == 0 ? 0 : 1;
}
+#ifdef CONFIG_SF_STM_PROTECT
+static int do_spi_protect(int argc, char * const argv[])
+{
+ int start, len, ret = 0;
+
+ if (argc != 4)
+ return -1;
+
+ start = simple_strtoull(argv[2], NULL, 16);
+ len = simple_strtoull(argv[3], NULL, 16);
+
+ if (strcmp(argv[1], "on") == 0)
+ ret = stm_lock(flash, start, len);
+ else if (strcmp(argv[1], "off") == 0)
+ ret = stm_unlock(flash, start, len);
+ else
+ return -1; /* Unknown parameter */
+
+ return ret == 0 ? 0 : 1;
+}
+#endif
+
#ifdef CONFIG_CMD_SF_TEST
enum {
STAGE_ERASE,
@@ -540,6 +562,10 @@ static int do_spi_flash(cmd_tbl_t *cmdtp, int flag, int argc,
ret = do_spi_flash_read_write(argc, argv);
else if (strcmp(cmd, "erase") == 0)
ret = do_spi_flash_erase(argc, argv);
+#ifdef CONFIG_SF_STM_PROTECT
+ else if (strcmp(cmd, "protect") == 0)
+ ret = do_spi_protect(argc, argv);
+#endif
#ifdef CONFIG_CMD_SF_TEST
else if (!strcmp(cmd, "test"))
ret = do_spi_flash_test(argc, argv);
@@ -579,5 +605,9 @@ U_BOOT_CMD(
"sf update addr offset|partition len - erase and write `len' bytes from memory\n"
" at `addr' to flash at `offset'\n"
" or to start of mtd `partition'\n"
+#ifdef CONFIG_SF_STM_PROTECT
+ "sf protect on/off sector len - protect/unprotect 'len' bytes starting\n"
+ " at address 'sector'\n"
+#endif
SF_TEST_HELP
);
@@ -162,12 +162,6 @@ int spi_flash_cmd_write(struct spi_slave *spi, const u8 *cmd, size_t cmd_len,
/* Flash erase(sectors) operation, support all possible erase commands */
int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len);
-/* Read the status register */
-int spi_flash_cmd_read_status(struct spi_flash *flash, u8 *rs);
-
-/* Program the status register */
-int spi_flash_cmd_write_status(struct spi_flash *flash, u8 ws);
-
/* Read the config register */
int spi_flash_cmd_read_config(struct spi_flash *flash, u8 *rc);
@@ -573,3 +573,191 @@ int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len,
return ret;
}
#endif
+
+#ifdef CONFIG_SF_STM_PROTECT
+#define SR_BP0 BIT(2) /* Block protect 0 */
+#define SR_BP1 BIT(3) /* Block protect 1 */
+#define SR_BP2 BIT(4) /* Block protect 2 */
+
+static void stm_get_locked_range(struct spi_flash *nor, u8 sr, loff_t *ofs,
+ u32 *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 = nor->size >> pow;
+ *ofs = nor->size - *len;
+ }
+}
+
+/*
+ * Return 1 if the entire region is locked, 0 otherwise
+ */
+static int stm_is_locked_sr(struct spi_flash *nor, loff_t ofs, u32 len,
+ u8 sr)
+{
+ loff_t lock_offs;
+ u32 lock_len;
+
+ stm_get_locked_range(nor, 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.
+ */
+int stm_is_locked(struct spi_flash *nor, loff_t ofs, u32 len)
+{
+ int status;
+ u8 sr;
+
+ status = spi_flash_cmd_read_status(nor, &sr);
+ if (status < 0)
+ return status;
+
+ return stm_is_locked_sr(nor, ofs, len, sr);
+}
+
+/*
+ * 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.
+ */
+int stm_lock(struct spi_flash *nor, u32 ofs, u32 len)
+{
+ u8 status_old, status_new;
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+ u8 shift = ffs(mask) - 1, pow, val;
+
+ spi_flash_cmd_read_status(nor, &status_old);
+
+ /* SPI NOR always locks to the end */
+ if (ofs + len != nor->size) {
+ /* Does combined region extend to end? */
+ if (!stm_is_locked_sr(nor, ofs + len, nor->size - ofs - len,
+ status_old))
+ return -EINVAL;
+ len = nor->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(nor->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;
+
+ spi_flash_cmd_write_status(nor, status_new);
+
+ return 0;
+}
+
+/*
+ * Unlock a region of the flash. See stm_lock() for more info
+ *
+ * Returns negative on errors, 0 on success.
+ */
+int stm_unlock(struct spi_flash *nor, u32 ofs, u32 len)
+{
+ uint8_t status_old, status_new;
+ u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+ u8 shift = ffs(mask) - 1, pow, val;
+
+ spi_flash_cmd_read_status(nor, &status_old);
+
+ /* Cannot unlock; would unlock larger region than requested */
+ if (stm_is_locked_sr(nor, status_old, ofs - nor->erase_size,
+ nor->erase_size))
+ 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(nor->size) - order_base_2(nor->size - (ofs + len));
+ if (ofs + len == nor->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;
+
+ spi_flash_cmd_write_status(nor, status_new);
+
+ return 0;
+}
+#else
+int stm_is_locked(struct spi_flash *nor, loff_t ofs, u32 len)
+{
+ return 0;
+}
+
+int stm_lock(struct spi_flash *nor, u32 ofs, u32 len)
+{
+ return 0;
+}
+
+int stm_unlock(struct spi_flash *nor, u32 ofs, u32 len)
+{
+ return 0;
+}
+#endif /* CONFIG_SF_STM_PROTECT */
@@ -115,6 +115,17 @@ struct dm_spi_flash_ops {
int (*erase)(struct udevice *dev, u32 offset, size_t len);
};
+
+/* Read the status register */
+int spi_flash_cmd_read_status(struct spi_flash *flash, u8 *rs);
+
+/* Program the status register */
+int spi_flash_cmd_write_status(struct spi_flash *flash, u8 ws);
+
+int stm_is_locked(struct spi_flash *nor, loff_t ofs, u32 len);
+int stm_lock(struct spi_flash *nor, u32 ofs, u32 len);
+int stm_unlock(struct spi_flash *nor, u32 ofs, u32 len);
+
/* Access the serial operations for a device */
#define sf_get_ops(dev) ((struct dm_spi_flash_ops *)(dev)->driver->ops)
@@ -219,13 +230,23 @@ static inline int spi_flash_read(struct spi_flash *flash, u32 offset,
static inline int spi_flash_write(struct spi_flash *flash, u32 offset,
size_t len, const void *buf)
{
- return flash->write(flash, offset, len, buf);
+ if (stm_is_locked(flash, offset, len) > 0) {
+ printf("offset 0x%x is protected and cannot be written\n", offset);
+ return -EINVAL;
+ } else {
+ return flash->write(flash, offset, len, buf);
+ }
}
static inline int spi_flash_erase(struct spi_flash *flash, u32 offset,
size_t len)
{
- return flash->erase(flash, offset, len);
+ if (stm_is_locked(flash, offset, len) > 0) {
+ printf("offset 0x%x is protected and cannot be erased\n", offset);
+ return -EINVAL;
+ } else {
+ return flash->erase(flash, offset, len);
+ }
}
#endif