Hello Vignesh, On Tue, Jun 18, 2019 at 9:55 AM Vignesh Raghavendra <vigneshr@xxxxxx> wrote: > > +Uwe who had interest in 4bit block protection support > > On 12-Jun-19 4:17 PM, Sagar Shrikant Kadam wrote: > > Implement a locking scheme for ISSI devices based on stm_lock mechanism. > > The is25xxxxx devices have 4 bits for selecting the range of blocks to > > be locked/protected from erase/write operations and function register > > gives feasibility to select TOP / Bottom area for protection. > > Added opcodes to read and write function registers. > > > > The current implementation enables block protection as per the table > > defined into datasheet for is25wp256 device having erase size of 0x1000. > > ISSI and stm devices differ in terms of TBS (Top/Bottom area protection) > > bits. In case of issi this is in Function register and is OTP memory, so > > once FR bits are programmed cannot be modified. > > > > I am not a fan of modifying/setting OTP bits are they are irreversible > and change the expectation of other SWs in the system such as > bootloader. See comments further down the patch.... > > > Some common code from stm_lock/unlock implementation is extracted so that > > it can be re-used for issi devices. The locking scheme has been tested on > > HiFive Unleashed board, having is25wp256 flash memory. > > > > Have you tested lock/unlock on non ISSI device with this series? > Sorry, I haven't tested this series on non ISSI devices. > > Signed-off-by: Sagar Shrikant Kadam <sagar.kadam@xxxxxxxxxx> > > --- > > drivers/mtd/spi-nor/spi-nor.c | 291 ++++++++++++++++++++++++++++++++++-------- > > include/linux/mtd/spi-nor.h | 5 + > > 2 files changed, 245 insertions(+), 51 deletions(-) > > > > diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c > > index b7c6261..9281ec0 100644 > > --- a/drivers/mtd/spi-nor/spi-nor.c > > +++ b/drivers/mtd/spi-nor/spi-nor.c > > @@ -288,6 +288,45 @@ struct flash_info { > > > > #define JEDEC_MFR(info) ((info)->id[0]) > > > > +/** > > + * read_fr() -read function register > > + * @nor: pointer to a 'struct spi_nor'. > > + * > > + * ISSI devices have top/bottom area protection bits selection into function > > + * reg.The bits in FR are OTP.So once it's written, it cannot be changed. > > + * > > + * Return: Value in function register or Negative if error. > > + */ > > +static int read_fr(struct spi_nor *nor) > > Please prefix spi_nor_ (spi_nor_read_fr()) to all generic functions that > you are adding in this patch > Ok. Can do as suggested in v6. > > +{ > > + int ret; > > + u8 val; > > + > > + ret = nor->read_reg(nor, SPINOR_OP_RDFR, &val, 1); > > + if (ret < 0) { > > + pr_err("error %d reading FR\n", (int) ret); > > dev_err() and no need to cast 'ret' to int > > > + return ret; > > + } > > + > > + return val; > > +} > > + > > +/** > > + * write_fr() -Write function register > > + * @nor: pointer to a 'struct spi_nor'. > > + * > > + * ISSI devices have top/bottom area selection protection bits into function > > + * reg whereas other devices have the TBS bit into Status Register. > s/into/in > > > + * The bits in FR are OTP.So once it's written, it cannot be changed. > > + * > > + * Return: Negative if error > > + */ > > +static int write_fr(struct spi_nor *nor, u8 val) > > +{ > > + nor->cmd_buf[0] = val; > > + return nor->write_reg(nor, SPINOR_OP_WRFR, nor->cmd_buf, 1); > > +} > > + > > /* > > * Read the status register, returning its value in the location > > * Return the status register value. > > @@ -1088,10 +1127,17 @@ 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; > > + u8 mask = 0; > > + int shift = 0; > > int pow; > > > > + if (JEDEC_MFR(nor->info) == SNOR_MFR_ISSI) > > + mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; > > Does all ISSI flashes support SR_BP3? Yes, I have checked a range of ISSI flash devices and they do support the fourth block protect bit (BP3). > Irrespective of that this isn't generic enough. There are non ISSI > flashes with BP3. Please add a flag or field to flash_info struct to > identify flashes with BP3 bit and then use combination of the flag and > MFR ID to select suitable lock/unlock mechanism > Ok. To make it more generic I will also introduce a macro into flash_info struct to indicate that a flash has BP3 bit and then use it accordingly. > > > + else > > + mask = SR_BP2 | SR_BP1 | SR_BP0; > > + > > + shift = ffs(mask) - 1; > > + > > if (!(sr & mask)) { > > /* No protection */ > > *ofs = 0; > > @@ -1099,10 +1145,19 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs, > > } else { > > pow = ((sr & mask) ^ mask) >> shift; > > *len = mtd->size >> pow; > > - if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) > > - *ofs = 0; > > - else > > - *ofs = mtd->size - *len; > > + > > + if (JEDEC_MFR(nor->info) == SNOR_MFR_ISSI) { > > + if (nor->flags & SNOR_F_HAS_SR_TB && > > + (read_fsr(nor) & FR_TB)) > > + *ofs = 0; > > + else > > + *ofs = mtd->size - *len; > > + } else { > > + if (nor->flags & SNOR_F_HAS_SR_TB && sr & SR_TB) > > + *ofs = 0; > > + else > > + *ofs = mtd->size - *len; > > + } > > } > > } > > > > @@ -1129,18 +1184,108 @@ static int stm_check_lock_status_sr(struct spi_nor *nor, loff_t ofs, uint64_t le > > return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs); > > } > > > > -static int stm_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > > +/* > > + * check if memory region is locked > > + * > > + * Returns false if region is locked 0 otherwise. > > + */ > > +static int fl_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > > u8 sr) > > { > > return stm_check_lock_status_sr(nor, ofs, len, sr, true); > > } > > > > -static int stm_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > > +/* > > + * check if memory region is unlocked > > + * > > + * Returns false if region is locked 0 otherwise. > > + */ > > +static int fl_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len, > > u8 sr) > > { > > return stm_check_lock_status_sr(nor, ofs, len, sr, false); > > } > > > > +/** > > + * flash_select_zone() - Select TOP area or bottom area to lock/unlock > > + * @nor: pointer to a 'struct spi_nor'. > > + * @ofs: offset from which to lock memory. > > + * @len: number of bytes to unlock. > > + * @sr: status register > > + * @tb: pointer to top/bottom bool used in caller function > > + * @op: zone selection is for lock/unlock operation. 1: lock 0:unlock > > + * > > + * Select the top area / bottom area paattern to protect memory blocks. > > s/paattern/pattern > Ok > > + * > > + * Returns negative on errors, 0 on success. > > + */ > > +static int fl_select_zone(struct spi_nor *nor, loff_t ofs, uint64_t len, > > + u8 sr, bool *tb, bool op) > > +{ > > + int retval; > > + bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > > + > > + if (op) { > > + /* Select for lock zone operation */ > > + > > + /* > > + * If nothing in our range is unlocked, we don't need > > + * to do anything. > > + */ > > + if (fl_is_locked_sr(nor, ofs, len, sr)) > > + return 0; > > + > > + /* > > + * If anything below us is unlocked, we can't use 'bottom' > > + * protection. > > + */ > > + if (!fl_is_locked_sr(nor, 0, ofs, sr)) > > + can_be_bottom = false; > > + > > + /* > > + * If anything above us is unlocked, we can't use 'top' > > + * protection. > > + */ > > + if (!fl_is_locked_sr(nor, ofs + len, > > + nor->mtd.size - (ofs + len), sr)) > > + can_be_top = false; > > + } else { > > + /* Select unlock zone */ > > + > > + /* > > + * If nothing in our range is locked, we don't need to > > + * do anything. > > + */ > > + if (fl_is_unlocked_sr(nor, ofs, len, sr)) > > + return 0; > > + > > + /* > > + * If anything below us is locked, we can't use 'top' > > + * protection > > + */ > > + if (!fl_is_unlocked_sr(nor, 0, ofs, sr)) > > + can_be_top = false; > > + > > + /* > > + * If anything above us is locked, we can't use 'bottom' > > + * protection > > + */ > > + if (!fl_is_unlocked_sr(nor, ofs + len, > > + nor->mtd.size - (ofs + len), sr)) > > + can_be_bottom = false; > > + } > > + > > + if (!can_be_bottom && !can_be_top) > > + retval = -EINVAL; > > + else { > > + /* Prefer top, if both are valid */ > > + *tb = can_be_top; > > + retval = 1; > > + } > > + > > + return retval; > > +} > > + > > /* > > * Lock a region of the flash. Compatible with ST Micro and similar flash. > > * Supports the block protection bits BP{0,1,2} in the status register > > @@ -1178,33 +1323,19 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) > > struct mtd_info *mtd = &nor->mtd; > > int status_old, status_new; > > u8 mask = SR_BP2 | SR_BP1 | SR_BP0; > > - u8 shift = ffs(mask) - 1, pow, val; > > + u8 shift = ffs(mask) - 1, pow, val, ret; > > loff_t lock_len; > > - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > > bool use_top; > > > > status_old = read_sr(nor); > > if (status_old < 0) > > return status_old; > > > > - /* If nothing in our range is unlocked, we don't need to do anything */ > > - if (stm_is_locked_sr(nor, ofs, len, status_old)) > > + ret = fl_select_zone(nor, ofs, len, status_old, &use_top, 1); > > + if (!ret) > > return 0; > > - > > - /* If anything below us is unlocked, we can't use 'bottom' protection */ > > - if (!stm_is_locked_sr(nor, 0, ofs, status_old)) > > - can_be_bottom = false; > > - > > - /* If anything above us is unlocked, we can't use 'top' protection */ > > - if (!stm_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len), > > - status_old)) > > - can_be_top = false; > > - > > - if (!can_be_bottom && !can_be_top) > > - return -EINVAL; > > - > > - /* Prefer top, if both are valid */ > > - use_top = can_be_top; > > + else if (ret < 0) > > + return ret; > > > > /* lock_len: length of region that should end up locked */ > > if (use_top) > > @@ -1258,35 +1389,21 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) > > struct mtd_info *mtd = &nor->mtd; > > int status_old, status_new; > > u8 mask = SR_BP2 | SR_BP1 | SR_BP0; > > - u8 shift = ffs(mask) - 1, pow, val; > > + u8 shift = ffs(mask) - 1, pow, val, ret; > > loff_t lock_len; > > - bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB; > > bool use_top; > > > > status_old = read_sr(nor); > > if (status_old < 0) > > return status_old; > > > > - /* If nothing in our range is locked, we don't need to do anything */ > > - if (stm_is_unlocked_sr(nor, ofs, len, status_old)) > > + ret = fl_select_zone(nor, ofs, len, status_old, &use_top, 0); > > + if (!ret) > > return 0; > > + else if (ret < 0) > > + return ret; > > > > - /* If anything below us is locked, we can't use 'top' protection */ > > - if (!stm_is_unlocked_sr(nor, 0, ofs, status_old)) > > - can_be_top = false; > > - > > - /* If anything above us is locked, we can't use 'bottom' protection */ > > - if (!stm_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len), > > - status_old)) > > - can_be_bottom = false; > > - > > - if (!can_be_bottom && !can_be_top) > > - return -EINVAL; > > - > > - /* Prefer top, if both are valid */ > > - use_top = can_be_top; > > - > > - /* lock_len: length of region that should remain locked */ > > + /* lock_len: length of region that should end up locked */ > > if (use_top) > > lock_len = mtd->size - (ofs + len); > > else > > @@ -1338,7 +1455,7 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len) > > * Returns 1 if entire region is locked, 0 if any portion is unlocked, and > > * negative on errors. > > */ > > -static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) > > +static int fl_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) > > { > > int status; > > > > @@ -1346,7 +1463,7 @@ static int stm_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) > > if (status < 0) > > return status; > > > > - return stm_is_locked_sr(nor, ofs, len, status); > > + return fl_is_locked_sr(nor, ofs, len, status); > > } > > > > static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) > > @@ -1461,6 +1578,77 @@ static int macronix_quad_enable(struct spi_nor *nor) > > } > > > > /** > > + * issi_lock() - set BP[0123] write-protection. > > + * @nor: pointer to a 'struct spi_nor'. > > + * @ofs: offset from which to lock memory. > > + * @len: number of bytes to unlock. > > + * > > + * Lock a region of the flash.Implementation is based on stm_lock > > + * Supports the block protection bits BP{0,1,2,3} in the status register > > + * > > + * Return: 0 on success, -errno otherwise. > > + */ > > +static int issi_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) > > +{ > > + int status_old, status_new, blk_prot; > > + u8 mask = SR_BP3 | SR_BP2 | SR_BP1 | SR_BP0; > > + u8 shift = ffs(mask) - 1; > > + u8 pow, ret, func_reg; > > + bool use_top; > > + loff_t lock_len; > > + > > + status_old = read_sr(nor); > > + > > + /* if status reg is Write protected don't update bit protection */ > > + if (status_old & SR_SRWD) { > > + dev_err(nor->dev, > > + "SR is Write Protected,can't update BP bits...\n"); > > + return -EINVAL; > > + } > > + > > + ret = fl_select_zone(nor, ofs, len, status_old, &use_top, 1); > > + if (!ret) > > + /* Older protected blocks include the new requested block's */ > > + return 0; > > + else if (ret < 0) > > + return ret; > > + > > + func_reg = read_fr(nor); > > + /* lock_len: length of region that should end up locked */ > > + if (use_top) { > > + /* Update Function register to use TOP area */ > > + if ((func_reg >> 1) & 0x1) { > > + /* Currently bootom selected change to top */ > > + func_reg ^= FR_TB; > > + write_fr(nor, func_reg); > > + } > > IIUC, since this FR_TB OTP bit is initially 0 and now reads 1, implies > that OTP bit has already been programmed once. So is clearing the bit > possible? > > I think this lock/unlock mechanism needs a bit more thought. > One solution would be to not modify OTP bit and return error in all > cases when locking a region requested by user is not possible (for a > default scheme). I do agree here, writing the OTP will refrain the user from changing the lock direction once it's set. So better not update the OTP bits. I will redo the approach and submit a v6 for this. Thanks for your valuable feedback. > Regards > Vignesh > Regards, Sagar Kadam > > + lock_len = nor->mtd.size - ofs; > > + } else { > > + > > + /* Update Function register to use bottom area */ > > + if (!((func_reg >> 1) & 0x1)) { > > + /*Currently top is selected, change to bottom */ > > + func_reg ^= FR_TB; > > + write_fr(nor, func_reg); > > + } > > + lock_len = ofs + len; > > + } > > + > > + pow = order_base_2(lock_len); > > + blk_prot = mask & (((pow+1) & 0xf)<<shift); > > + if (lock_len <= 0) { > > + dev_err(nor->dev, "invalid Length to protect"); > > + return -EINVAL; > > + } > > + > > + status_new = status_old | blk_prot; > > + if (status_old == status_new) > > + return 0; > > + > > + return write_sr_and_check(nor, status_new, mask); > > +} > > + > > +/** > > * issi_unlock() - clear BP[0123] write-protection. > > * @nor: pointer to a 'struct spi_nor'. > > * @ofs: offset from which to unlock memory. > > @@ -1879,7 +2067,7 @@ static int sr2_bit7_quad_enable(struct spi_nor *nor) > > SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, > > { "is25wp256", INFO(0x9d7019, 0, 64 * 1024, 1024, > > SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | > > - SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK) > > + SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) > > }, > > > > /* Macronix */ > > @@ -4120,12 +4308,13 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, > > info->flags & SPI_NOR_HAS_LOCK) { > > nor->flash_lock = stm_lock; > > nor->flash_unlock = stm_unlock; > > - nor->flash_is_locked = stm_is_locked; > > + nor->flash_is_locked = fl_is_locked; > > } > > > > /* NOR protection support for ISSI chips */ > > if (JEDEC_MFR(info) == SNOR_MFR_ISSI || > > info->flags & SPI_NOR_HAS_LOCK) { > > + nor->flash_lock = issi_lock; > > nor->flash_unlock = issi_unlock; > > > > } > > diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h > > index 9a7d719..a15d012 100644 > > --- a/include/linux/mtd/spi-nor.h > > +++ b/include/linux/mtd/spi-nor.h > > @@ -40,6 +40,8 @@ > > #define SPINOR_OP_RDSR 0x05 /* Read status register */ > > #define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */ > > #define SPINOR_OP_RDSR2 0x3f /* Read status register 2 */ > > +#define SPINOR_OP_RDFR 0x48 /* Read Function register */ > > +#define SPINOR_OP_WRFR 0x42 /* Write Function register 1 byte */ > > #define SPINOR_OP_WRSR2 0x3e /* Write status register 2 */ > > #define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */ > > #define SPINOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */ > > @@ -139,6 +141,9 @@ > > /* Enhanced Volatile Configuration Register bits */ > > #define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */ > > > > +/*Function register bit */ > > +#define FR_TB BIT(1) /*ISSI: Top/Bottom protect */ > > + > > /* Flag Status Register bits */ > > #define FSR_READY BIT(7) /* Device status, 0 = Busy, 1 = Ready */ > > #define FSR_E_ERR BIT(5) /* Erase operation status */ > > ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/