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

From: Fabio Estevam fabio.estevam@freescale.com
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 v4: - 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..

From: Fabio Estevam fabio.estevam@freescale.com
Add the SPI NOR protection mechanism from the kernel.
This code is based on the work from Brian Norris computersforpeace@gmail.com https://patchwork.ozlabs.org/patch/513041/
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com --- Changes since v4: - Use CONFIG_SPI_FLASH_STMICRO
drivers/mtd/spi/sf_ops.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+)
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_STMICRO +#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_STMICRO */

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 and write operations, making sure that the protected areas are skipped.
Tested on a mx6qsabresd:
=> sf probe SF: Detected M25P32 with page size 256 Bytes, erase size 64 KiB, total 4 MiB => sf protect lock 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 offset 0x3f0000 is protected and cannot be erased SF: 65536 bytes @ 0x3f0000 Erased: ERROR => sf protect unlock 0x3f0000 0x10000 => sf erase 0x3f0000 0x10000 SF: 65536 bytes @ 0x3f0000 Erased: OK
Signed-off-by: Fabio Estevam fabio.estevam@freescale.com --- Changes since v4: - Use sf protect lock/unlock (Jagan) - Remove SPI_FLASH_STM_PROTECT (Jagan) - Do not remove definitions from drivers/mtd/spi/sf_internal.h (Jagan) - Move other prototypes to drivers/mtd/spi/sf_internal.h (Jagan)
common/cmd_sf.c | 35 +++++++++++++++++++++++++++++++++++ drivers/mtd/spi/sf_internal.h | 8 ++++++++ drivers/mtd/spi/sf_ops.c | 33 +++++++++++++++++++++++++++++++++ drivers/mtd/spi/sf_probe.c | 27 +++++++++++++++++++++++++++ include/spi_flash.h | 7 +++++++ 5 files changed, 110 insertions(+)
diff --git a/common/cmd_sf.c b/common/cmd_sf.c index ac7f5df..42862d9 100644 --- a/common/cmd_sf.c +++ b/common/cmd_sf.c @@ -348,6 +348,37 @@ static int do_spi_flash_erase(int argc, char * const argv[]) return ret == 0 ? 0 : 1; }
+static int do_spi_protect(int argc, char * const argv[]) +{ + int ret = 0; + loff_t start, len; + bool prot = false; + + if (argc != 4) + return -1; + + if (!str2off(argv[2], &start)) { + puts("start sector is not a valid number\n"); + return 1; + } + + if (!str2off(argv[3], &len)) { + puts("len is not a valid number\n"); + return 1; + } + + if (strcmp(argv[1], "lock") == 0) + prot = true; + else if (strcmp(argv[1], "unlock") == 0) + prot = false; + else + return -1; /* Unknown parameter */ + + ret = spi_flash_protect(flash, start, len, prot); + + return ret == 0 ? 0 : 1; +} + #ifdef CONFIG_CMD_SF_TEST enum { STAGE_ERASE, @@ -540,6 +571,8 @@ 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); + else if (strcmp(cmd, "protect") == 0) + ret = do_spi_protect(argc, argv); #ifdef CONFIG_CMD_SF_TEST else if (!strcmp(cmd, "test")) ret = do_spi_flash_test(argc, argv); @@ -579,5 +612,7 @@ 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" + "sf protect lock/unlock sector len - protect/unprotect 'len' bytes starting\n" + " at address 'sector'\n" SF_TEST_HELP ); diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h index 9c95d56..33be598 100644 --- a/drivers/mtd/spi/sf_internal.h +++ b/drivers/mtd/spi/sf_internal.h @@ -168,6 +168,10 @@ 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); + /* Read the config register */ int spi_flash_cmd_read_config(struct spi_flash *flash, u8 *rc);
@@ -222,6 +226,10 @@ int spi_flash_read_common(struct spi_flash *flash, const u8 *cmd, int spi_flash_cmd_read_ops(struct spi_flash *flash, u32 offset, size_t len, void *data);
+int spi_flash_cmd_lock_ops(struct spi_flash *flash, u32 offset, size_t len); +int spi_flash_cmd_unlock_ops(struct spi_flash *flash, u32 offset, size_t len); +int spi_flash_cmd_is_locked_ops(struct spi_flash *flash, u32 offset, size_t len); + #ifdef CONFIG_SPI_FLASH_MTD int spi_flash_mtd_register(struct spi_flash *flash); void spi_flash_mtd_unregister(void); diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 0f7a31d..d2b9ddc 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -276,6 +276,11 @@ int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) return -1; }
+ if (flash->is_locked(flash, offset, len) > 0) { + printf("offset 0x%x is protected and cannot be erased\n", offset); + return -EINVAL; + } + cmd[0] = flash->erase_cmd; while (len) { erase_addr = offset; @@ -318,6 +323,11 @@ int spi_flash_cmd_write_ops(struct spi_flash *flash, u32 offset,
page_size = flash->page_size;
+ if (flash->is_locked(flash, offset, len) > 0) { + printf("offset 0x%x is protected and cannot be written\n", offset); + return -EINVAL; + } + cmd[0] = flash->write_cmd; for (actual = 0; actual < len; actual += chunk_len) { write_addr = offset; @@ -356,6 +366,21 @@ int spi_flash_cmd_write_ops(struct spi_flash *flash, u32 offset, return ret; }
+int spi_flash_cmd_lock_ops(struct spi_flash *flash, u32 offset, size_t len) +{ + return stm_lock(flash, offset, len); +} + +int spi_flash_cmd_unlock_ops(struct spi_flash *flash, u32 offset, size_t len) +{ + return stm_unlock(flash, offset, len); +} + +int spi_flash_cmd_is_locked_ops(struct spi_flash *flash, u32 offset, size_t len) +{ + return stm_is_locked(flash, offset, len); +} + int spi_flash_read_common(struct spi_flash *flash, const u8 *cmd, size_t cmd_len, void *data, size_t data_len) { @@ -761,3 +786,11 @@ int stm_unlock(struct spi_flash *nor, u32 ofs, u32 len) return 0; } #endif /* CONFIG_SPI_FLASH_STMICRO */ + +int spi_flash_protect(struct spi_flash *flash, loff_t ofs, u32 len, bool prot) +{ + if (prot) + return flash->lock(flash, ofs, len); + else + return flash->unlock(flash, ofs, len); +} diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index 954376d..6da9d6c 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -149,6 +149,9 @@ static int spi_flash_validate_params(struct spi_slave *spi, u8 *idcode, #endif flash->erase = spi_flash_cmd_erase_ops; flash->read = spi_flash_cmd_read_ops; + flash->lock = spi_flash_cmd_lock_ops; + flash->unlock = spi_flash_cmd_unlock_ops; + flash->is_locked = spi_flash_cmd_is_locked_ops; #endif
/* Compute the flash size */ @@ -469,6 +472,27 @@ int spi_flash_std_erase(struct udevice *dev, u32 offset, size_t len) return spi_flash_cmd_erase_ops(flash, offset, len); }
+int spi_flash_std_lock(struct udevice *dev, u32 offset, size_t len) +{ + struct spi_flash *flash = dev_get_uclass_priv(dev); + + return spi_flash_cmd_lock_ops(flash, offset, len); +} + +int spi_flash_std_unlock(struct udevice *dev, u32 offset, size_t len) +{ + struct spi_flash *flash = dev_get_uclass_priv(dev); + + return spi_flash_cmd_unlock_ops(flash, offset, len); +} + +int spi_flash_std_is_locked(struct udevice *dev, u32 offset, size_t len) +{ + struct spi_flash *flash = dev_get_uclass_priv(dev); + + return spi_flash_cmd_is_locked_ops(flash, offset, len); +} + int spi_flash_std_probe(struct udevice *dev) { struct spi_slave *slave = dev_get_parentdata(dev); @@ -485,6 +509,9 @@ static const struct dm_spi_flash_ops spi_flash_std_ops = { .read = spi_flash_std_read, .write = spi_flash_std_write, .erase = spi_flash_std_erase, + .lock = spi_flash_std_lock, + .unlock = spi_flash_std_unlock, + .is_locked = spi_flash_std_is_locked, };
static const struct udevice_id spi_flash_std_ids[] = { diff --git a/include/spi_flash.h b/include/spi_flash.h index 3b2d555..3a9b399 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -105,6 +105,9 @@ struct spi_flash { int (*write)(struct spi_flash *flash, u32 offset, size_t len, const void *buf); int (*erase)(struct spi_flash *flash, u32 offset, size_t len); + int (*lock)(struct spi_flash *flash, u32 offset, size_t len); + int (*unlock)(struct spi_flash *flash, u32 offset, size_t len); + int (*is_locked)(struct spi_flash *flash, u32 offset, size_t len); #endif };
@@ -113,6 +116,9 @@ struct dm_spi_flash_ops { int (*write)(struct udevice *dev, u32 offset, size_t len, const void *buf); int (*erase)(struct udevice *dev, u32 offset, size_t len); + int (*lock)(struct udevice *dev, u32 offset, size_t len); + int (*unlock)(struct udevice *dev, u32 offset, size_t len); + int (*is_locked)(struct udevice *dev, u32 offset, size_t len); };
/* Access the serial operations for a device */ @@ -229,6 +235,7 @@ static inline int spi_flash_erase(struct spi_flash *flash, u32 offset, } #endif
+int spi_flash_protect(struct spi_flash *flash, loff_t ofs, u32 len, bool prot); void spi_boot(void) __noreturn; void spi_spl_load_image(uint32_t offs, unsigned int size, void *vdst);
participants (1)
-
Fabio Estevam