[U-Boot] [PATCH v2 1/2] bitops: Add support for order_base_2()

Add support for the order_base_2() macro (and its dependencies) from the Linux kernel.
This is useful for the SPI NOR unlock function.
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com --- Changes since v1: - None.
arch/arm/include/asm/bitops.h | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+)
diff --git a/arch/arm/include/asm/bitops.h b/arch/arm/include/asm/bitops.h index 9b78043..b9b6d21 100644 --- a/arch/arm/include/asm/bitops.h +++ b/arch/arm/include/asm/bitops.h @@ -108,6 +108,62 @@ static inline int __ilog2(unsigned int x) return generic_fls(x) - 1; }
+static inline int fls64(__u64 x) +{ + if (x == 0) + return 0; + return generic_fls(x) + 1; +} + +static inline unsigned fls_long(unsigned long l) +{ + if (sizeof(l) == 4) + return generic_fls(l); + return fls64(l); +} + +/* + * round up to nearest power of two + */ +static inline __attribute__((const)) +unsigned long __roundup_pow_of_two(unsigned long n) +{ + return 1UL << fls_long(n - 1); +} + +/** + * roundup_pow_of_two - round the given value up to nearest power of two + * @n - parameter + * + * round the given value up to the nearest power of two + * - the result is undefined when n == 0 + * - this can be used to initialise global variables from constant data + */ +#define roundup_pow_of_two(n) \ +( \ + __builtin_constant_p(n) ? ( \ + (n == 1) ? 1 : \ + (1UL << (__ilog2((n) - 1) + 1)) \ + ) : \ + __roundup_pow_of_two(n) \ +) + +/** + * order_base_2 - calculate the (rounded up) base 2 order of the argument + * @n: parameter + * + * The first few values calculated by this routine: + * ob2(0) = 0 + * ob2(1) = 0 + * ob2(2) = 1 + * ob2(3) = 2 + * ob2(4) = 2 + * ob2(5) = 3 + * ... and so on. + */ + +#define order_base_2(n) __ilog2(roundup_pow_of_two(n)) + /* * ffz = Find First Zero in word. Undefined if no zero exists, * so code should check against ~0UL first..

Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_FLASH_STM_PROTECT option that can be selectedby systems that want to protect regions of SPI NOR flash using the same programming model as in the ST Micro SPI NOR flashes, like for example the M25P32.
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
Tested on a mx6qsabresd:
=> sf probe SF: Detected M25P32 with page size 256 Bytes, erase size 64 KiB, total 4 MiB => sf protect on 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 offset 0x3f0000 is protected and cannot be erased SF: 65536 bytes @ 0x3f0000 Erased: ERROR => sf protect off 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 SF: 65536 bytes @ 0x3f0000 Erased: OK
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com --- Changes since v1: - Add CONFIG_SPI_FLASH_STM_PROTECT in the Kconfig as suggested by Stefan Roese
common/cmd_sf.c | 30 +++++++ drivers/mtd/spi/Kconfig | 15 ++++ drivers/mtd/spi/sf_internal.h | 6 -- drivers/mtd/spi/sf_ops.c | 188 ++++++++++++++++++++++++++++++++++++++++++ include/spi_flash.h | 25 +++++- 5 files changed, 256 insertions(+), 8 deletions(-)
diff --git a/common/cmd_sf.c b/common/cmd_sf.c index ac7f5df..a31243f 100644 --- a/common/cmd_sf.c +++ b/common/cmd_sf.c @@ -348,6 +348,28 @@ static int do_spi_flash_erase(int argc, char * const argv[]) return ret == 0 ? 0 : 1; }
+#ifdef CONFIG_SPI_FLASH_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_SPI_FLASH_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_SPI_FLASH_STM_PROTECT + "sf protect on/off sector len - protect/unprotect 'len' bytes starting\n" + " at address 'sector'\n" +#endif SF_TEST_HELP ); diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig index 3f7433c..2ee1089 100644 --- a/drivers/mtd/spi/Kconfig +++ b/drivers/mtd/spi/Kconfig @@ -101,6 +101,21 @@ config SPI_FLASH_USE_4K_SECTORS Please note that some tools/drivers/filesystems may not work with 4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
+config SPI_FLASH_STM_PROTECT + bool "Use STM flash protection mechanism" + depends on SPI_FLASH + help + 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 + config SPI_FLASH_DATAFLASH bool "AT45xxx DataFlash support" depends on SPI_FLASH && DM_SPI_FLASH diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 9c95d56..e66a62b 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -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);
diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..e12f8ee 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -573,3 +573,191 @@ int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len, return ret; } #endif + +#ifdef CONFIG_SPI_FLASH_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_SPI_FLASH_STM_PROTECT */ diff --git a/include/spi_flash.h b/include/spi_flash.h index 3b2d555..434323e 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -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

On 29 September 2015 at 17:37, Fabio Estevam fabio.estevam@freescale.com wrote:
Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_FLASH_STM_PROTECT option that can be selectedby systems that want to protect regions of SPI NOR flash using the same programming model as in the ST Micro SPI NOR flashes, like for example the M25P32.
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
This patch is still under review, will see how it moves.
Tested on a mx6qsabresd:
=> sf probe SF: Detected M25P32 with page size 256 Bytes, erase size 64 KiB, total 4 MiB => sf protect on 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 offset 0x3f0000 is protected and cannot be erased SF: 65536 bytes @ 0x3f0000 Erased: ERROR => sf protect off 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 SF: 65536 bytes @ 0x3f0000 Erased: OK
What if sf erase 0x3f0000 0x20000 - it should skip first 0x10000 from 0x3f0000 and then erase next 0x10000 from (0x3f0000+0x10000) did you verify this?
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com
Changes since v1:
- Add CONFIG_SPI_FLASH_STM_PROTECT in the Kconfig as suggested by
Stefan Roese
common/cmd_sf.c | 30 +++++++ drivers/mtd/spi/Kconfig | 15 ++++ drivers/mtd/spi/sf_internal.h | 6 -- drivers/mtd/spi/sf_ops.c | 188 ++++++++++++++++++++++++++++++++++++++++++ include/spi_flash.h | 25 +++++- 5 files changed, 256 insertions(+), 8 deletions(-)
diff --git a/common/cmd_sf.c b/common/cmd_sf.c index ac7f5df..a31243f 100644 --- a/common/cmd_sf.c +++ b/common/cmd_sf.c @@ -348,6 +348,28 @@ static int do_spi_flash_erase(int argc, char * const argv[]) return ret == 0 ? 0 : 1; }
+#ifdef CONFIG_SPI_FLASH_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_SPI_FLASH_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_SPI_FLASH_STM_PROTECT
"sf protect on/off sector len - protect/unprotect 'len' bytes starting\n"
" at address 'sector'\n"
+#endif SF_TEST_HELP ); diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig index 3f7433c..2ee1089 100644 --- a/drivers/mtd/spi/Kconfig +++ b/drivers/mtd/spi/Kconfig @@ -101,6 +101,21 @@ config SPI_FLASH_USE_4K_SECTORS Please note that some tools/drivers/filesystems may not work with 4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
+config SPI_FLASH_STM_PROTECT
bool "Use STM flash protection mechanism"
depends on SPI_FLASH
help
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
config SPI_FLASH_DATAFLASH bool "AT45xxx DataFlash support" depends on SPI_FLASH && DM_SPI_FLASH diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 9c95d56..e66a62b 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -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);
diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..e12f8ee 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -573,3 +573,191 @@ int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len, return ret; } #endif
+#ifdef CONFIG_SPI_FLASH_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_SPI_FLASH_STM_PROTECT */ diff --git a/include/spi_flash.h b/include/spi_flash.h index 3b2d555..434323e 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -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
-- 1.9.1
-- Jagan.

On Tue, Sep 29, 2015 at 10:54 AM, Jagan Teki jteki@openedev.com wrote:
What if sf erase 0x3f0000 0x20000 - it should skip first 0x10000 from 0x3f0000 and then erase next 0x10000 from (0x3f0000+0x10000) did you verify this?
On my case (M25P32) the 0x3f0000 is the last sector (sector size is 0x10000), so we cannot pass 0x20000.
Doing so we correctly get the error below:
=> sf erase 0x3f0000 0x20000 ERROR: attempting erase past flash size (0x400000)
Regards,
Fabio Estevam

Hi Jagan,
On Tue, Sep 29, 2015 at 10:54 AM, Jagan Teki jteki@openedev.com wrote:
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
This patch is still under review, will see how it moves.
Brian's patch is in linux-next now: https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/driv...
I have made some changes to the header files so that it can build for all archs and prepared v7.
Regards,
Fabio Estevam

On Fri, Oct 16, 2015 at 10:40 AM, Fabio Estevam festevam@gmail.com wrote:
On Tue, Sep 29, 2015 at 10:54 AM, Jagan Teki jteki@openedev.com wrote:
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
This patch is still under review, will see how it moves.
Brian's patch is in linux-next now: https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/driv...
I have made some changes to the header files so that it can build for all archs and prepared v7.
Is it applicable for 2015.10?

On 16 October 2015 at 19:20, Otavio Salvador otavio.salvador@ossystems.com.br wrote:
On Fri, Oct 16, 2015 at 10:40 AM, Fabio Estevam festevam@gmail.com wrote:
On Tue, Sep 29, 2015 at 10:54 AM, Jagan Teki jteki@openedev.com wrote:
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
This patch is still under review, will see how it moves.
Brian's patch is in linux-next now: https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/driv...
I have made some changes to the header files so that it can build for all archs and prepared v7.
Is it applicable for 2015.10?
Not sure, I've some comments about spi-nor changes will send it now (can be resolved easily) and also there is one dependent patch[1] about log2 and fls64 which is of generic one need a review comments from Tom or someone.
As this is a new feature and initial version submitted after the MW[2] usually new feature will be acceptable during the MW. I think it's good to proceed with, what ever time it got finalized during or after 2015.10.
[1] https://patchwork.ozlabs.org/patch/531313/ [2] https://patchwork.ozlabs.org/patch/494624/
-- Jagan.

On 29 September 2015 at 17:37, Fabio Estevam fabio.estevam@freescale.com wrote:
Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_FLASH_STM_PROTECT option that can be selectedby systems that want to protect regions of SPI NOR flash using the same programming model as in the ST Micro SPI NOR flashes, like for example the M25P32.
Based on the implementation from Brian Norris computersforpeace@gmail.com for the Linux kernel: https://patchwork.ozlabs.org/patch/513041/
Tested on a mx6qsabresd:
=> sf probe SF: Detected M25P32 with page size 256 Bytes, erase size 64 KiB, total 4 MiB => sf protect on 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 offset 0x3f0000 is protected and cannot be erased SF: 65536 bytes @ 0x3f0000 Erased: ERROR => sf protect off 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 SF: 65536 bytes @ 0x3f0000 Erased: OK
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com
Changes since v1:
- Add CONFIG_SPI_FLASH_STM_PROTECT in the Kconfig as suggested by
Stefan Roese
common/cmd_sf.c | 30 +++++++ drivers/mtd/spi/Kconfig | 15 ++++ drivers/mtd/spi/sf_internal.h | 6 -- drivers/mtd/spi/sf_ops.c | 188 ++++++++++++++++++++++++++++++++++++++++++ include/spi_flash.h | 25 +++++- 5 files changed, 256 insertions(+), 8 deletions(-)
diff --git a/common/cmd_sf.c b/common/cmd_sf.c index ac7f5df..a31243f 100644 --- a/common/cmd_sf.c +++ b/common/cmd_sf.c @@ -348,6 +348,28 @@ static int do_spi_flash_erase(int argc, char * const argv[]) return ret == 0 ? 0 : 1; }
+#ifdef CONFIG_SPI_FLASH_STM_PROTECT
Drop this vendor specific macro on command code (usually command code deals generic-ness)
+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);
Use proper endptr - see the sample code on same file.
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 */
Again stm_* vendor calls, use spi_flash_protect(flash, start, end, prot) then in spi_flash.c call functions flash->lock and flash->unlock like read/write ops.
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_SPI_FLASH_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_SPI_FLASH_STM_PROTECT
"sf protect on/off sector len - protect/unprotect 'len' bytes starting\n"
" at address 'sector'\n"
+#endif SF_TEST_HELP ); diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig index 3f7433c..2ee1089 100644 --- a/drivers/mtd/spi/Kconfig +++ b/drivers/mtd/spi/Kconfig @@ -101,6 +101,21 @@ config SPI_FLASH_USE_4K_SECTORS Please note that some tools/drivers/filesystems may not work with 4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
+config SPI_FLASH_STM_PROTECT
bool "Use STM flash protection mechanism"
depends on SPI_FLASH
help
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
config SPI_FLASH_DATAFLASH bool "AT45xxx DataFlash support" depends on SPI_FLASH && DM_SPI_FLASH diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 9c95d56..e66a62b 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -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);
diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..e12f8ee 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -573,3 +573,191 @@ int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len, return ret; } #endif
+#ifdef CONFIG_SPI_FLASH_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_SPI_FLASH_STM_PROTECT */ diff --git a/include/spi_flash.h b/include/spi_flash.h index 3b2d555..434323e 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -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);
}
Please handle protect check on spi_ops instead of spi_flash, like check the whether the sector is protected or not before erasing it.
} #endif
-- 1.9.1
thanks!

On Tue, Sep 29, 2015 at 6:12 PM, Jagan Teki jteki@openedev.com wrote:
+#ifdef CONFIG_SPI_FLASH_STM_PROTECT
Drop this vendor specific macro on command code (usually command code deals generic-ness)
Ok
+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);
Use proper endptr - see the sample code on same file.
Will use str2off function which automatically handles endptr.
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 */
Again stm_* vendor calls, use spi_flash_protect(flash, start, end, prot) then in spi_flash.c call functions flash->lock and flash->unlock like read/write ops.
Ok
Please handle protect check on spi_ops instead of spi_flash, like check the whether the sector is protected or not before erasing it.
Ok.
Addressed your comments in v3.
Thanks for your review.
Regards,
Fabio Estevam
participants (4)
-
Fabio Estevam
-
Fabio Estevam
-
Jagan Teki
-
Otavio Salvador