[PATCH v6 5/5] mtd: spi-nor: keep lock bits if they are non-volatile

Michael Walle michael at walle.cc
Mon Nov 30 09:38:18 EST 2020


Am 2020-11-28 11:17, schrieb Tudor.Ambarus at microchip.com:
> On 11/26/20 10:26 PM, Michael Walle wrote:
>> EXTERNAL EMAIL: Do not click links or open attachments unless you know 
>> the content is safe
>> 
>> Traditionally, linux unlocks the whole flash because there are legacy
>> devices which has the write protections bits set by default at 
>> startup.
>> If you actually want to use the flash protection bits, eg. because 
>> there
>> is a read-only part for a bootloader, this automatic unlocking is
>> harmful. If there is no hardware write protection in place (usually
>> called WP#), a startup of the kernel just discards this protection.
>> 
>> I've gone through the datasheets of all the flashes (except the Intel
>> ones where I could not find any datasheet nor reference) which 
>> supports
>> the unlocking feature and looked how the sector protection was
>> implemented. The currently supported flashes can be divided into the
>> following two categories:
>>  (1) block protection bits are non-volatile. Thus they keep their 
>> values
>>      at reset and power-cycle
>>  (2) flashes where these bits are volatile. After reset or 
>> power-cycle,
>>      the whole memory array is protected.
>>      (a) some devices needs a special "Global Unprotect" command, eg.
>>          the Atmel AT25DF041A.
>>      (b) some devices require to clear the BPn bits in the status
>>          register.
>> 
>> Due to the reasons above, we do not want to clear the bits for flashes
>> which belong to category (1). Fortunately for us, only Atmel flashes
>> fall into category (2a). Implement the "Global Protect" and "Global
>> Unprotect" commands for these. For (2b) we can use normal block
>> protection locking scheme.
>> 
>> This patch adds a new flag to indicate the case (2). Only if we have
>> such a flash we unlock the whole flash array. To be backwards 
>> compatible
>> it also introduces a kernel configuration option which restores the
>> complete legacy behavior ("Disable write protection on any flashes").
>> Hopefully, this will clean up "unlock the entire flash for legacy
>> devices" once and for all.
>> 
>> For reference here are the actually commits which introduced the 
>> legacy
>> behaviour (and extended the behaviour to other chip manufacturers):
> 
> typo: behavior
> 
>> 
>> commit f80e521c916cb ("mtd: m25p80: add support for the Intel/Numonyx 
>> {16,32,64}0S33B SPI flash chips")
>> commit ea60658a08f8f ("mtd: m25p80: disable SST software protection 
>> bits by default")
>> commit 7228982442365 ("[MTD] m25p80: fix bug - ATmel spi flash fails 
>> to be copied to")
>> 
>> Actually, this might also fix handling of the Atmel AT25DF flashes,
>> because the original commit 7228982442365 ("[MTD] m25p80: fix bug -
>> ATmel spi flash fails to be copied to") was writing a 0 to the status
>> register, which is a "Global Unprotect". This might not be the case in
>> the current code which only handles the block protection bits BP2, BP1
>> and BP0. Thus, it depends on the current contents of the status 
>> register
>> if this unlock actually corresponds to a "Global Unprotect" command. 
>> In
>> the worst case, the current code might leave the AT25DF flashes in a
>> write protected state.
>> 
>> The commit 191f5c2ed4b6f ("mtd: spi-nor: use 16-bit WRR command when 
>> QE
>> is set on spansion flashes") changed that behaviour by just clearing 
>> BP2
>> to BP0 instead of writing a 0 to the status register.
>> 
>> Further, the commit 3e0930f109e76 ("mtd: spi-nor: Rework the disabling
>> of block write protection") expanded the unlock_all() feature to ANY
>> flash which supports locking.
>> 
>> Signed-off-by: Michael Walle <michael at walle.cc>
>> ---
>> changes since v5:
>>  - also set SRWD bit for the "Global Protect" command
>>  - use spi_nor_write_sr() instead of spi_nor_write_sr_and_check() to 
>> send
>>    the "Global Protect" or "Global Unprotect" command
>>  - mark ESMT F25L32QA as non-volatile as indicated in a newer 
>> datasheet
>>    revision
>>  - rebased to latest tree
>> 
>> changes since v4:
>>  - made atmel_global_protection_default_init() static, spotted by
>>    lkp at intel.com
>> 
>> changes since v3:
>>  - now defaulting to MTD_SPI_NOR_WP_DISABLE_ON_VOLATILE, suggested by 
>> Vignesh
>>  - restored the original spi_nor_unlock_all(), instead add individual
>>    locking ops for the "Global Protect" scheme in atmel.c. This was 
>> tested
>>    partly with the AT25SL321 (for the test I added the fixups to this
>>    flash).
>>  - renamed SPI_NOR_UNPROTECT to SPI_NOR_WP_IS_VOLATILE. Suggested by
>>    Vingesh, although I've renamed it to a more general 
>> "WP_IS_VOLATILE"
>>    because either the BP bits or the individual sector locks might be
>>    volatile.
>>  - add mention of both block protection bits and "Global Unprotect" 
>> command
>>    in the Kconfig help text.
>> 
>> changes since v2:
>>  - add Kconfig option to be able to retain legacy behaviour
>>  - rebased the patch due to the spi-nor rewrite
>>  - dropped the Fixes: tag, it doens't make sense after the spi-nor 
>> rewrite
>>  - mention commit 3e0930f109e76 which further modified the unlock
>>    behaviour.
>> 
>> changes since v1:
>>  - completely rewrote patch, the first version used a device tree flag
>> 
>>  drivers/mtd/spi-nor/Kconfig |  42 ++++++++++++
>>  drivers/mtd/spi-nor/atmel.c | 127 
>> ++++++++++++++++++++++++++++++++++--
>>  drivers/mtd/spi-nor/core.c  |  36 ++++++----
>>  drivers/mtd/spi-nor/core.h  |   8 +++
>>  drivers/mtd/spi-nor/esmt.c  |   2 +-
>>  drivers/mtd/spi-nor/intel.c |   9 ++-
>>  drivers/mtd/spi-nor/sst.c   |  21 +++---
>>  7 files changed, 213 insertions(+), 32 deletions(-)
>> 
>> diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
>> index ffc4b380f2b1..11e6658ee85d 100644
>> --- a/drivers/mtd/spi-nor/Kconfig
>> +++ b/drivers/mtd/spi-nor/Kconfig
>> @@ -24,6 +24,48 @@ config MTD_SPI_NOR_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).
>> 
>> +choice
>> +       prompt "Write protection at boot"
>> +       default MTD_SPI_NOR_WP_DISABLE_ON_VOLATILE
>> +
>> +config MTD_SPI_NOR_WP_DISABLE
> 
> Maybe it's just me, but when I see WP, I think about the WP# signal, 
> which is
> somehow related. I think I would prefer to use  SWP instead, which 
> comes from
> Software Write Protection, which should be good for both the BPn 
> protection
> and for the Individual Sector Protection with its Global Lock and 
> Unlock.

I don't know either. Somehow the SWP will become a true hw write 
protection
with the WP# pin. But I tend to agree, I'll change it to SWP.

> 
> I won't stall the series just for this, so do as you prefer.
> 
>> +       bool "Disable WP on any flashes (legacy behaviour)"
> 
> typo: behavior

Just out of curiosity, is kernel doc strict American English?

> 
>> +       help
>> +         This option disables the write protection on any SPI flashes 
>> at
> 
> If you'll choose SWP, you have to update description here and there. 
> For
> example s/write protection/software write protection.
> 
>> +         boot-up.
>> +
>> +         Depending on the flash chip this either clears the block 
>> protection
>> +         bits or does a "Global Unprotect" command.
>> +
>> +         Don't use this if you intent to use the write protection of 
>> your
>> +         SPI flash. This is only to keep backwards compatibility.
>> +
>> +config MTD_SPI_NOR_WP_DISABLE_ON_VOLATILE
>> +       bool "Disable WP on flashes w/ volatile protection bits"
>> +       help
>> +         Some SPI flashes have volatile block protection bits, ie. 
>> after a
>> +         power-up or a reset the flash is write protected by default.
>> +
>> +         This option disables the write protection for these kind of 
>> flashes
>> +         while keeping it enabled for any other SPI flashes which 
>> have
>> +         non-volatile write protection bits.
>> +
>> +         If the write protection will be disabled depending on the 
>> flash
>> +         either the block protection bits are cleared or a "Global 
>> Unprotect"
>> +         command is issued.
>> +
>> +         If you are unsure, select this option.
>> +
>> +config MTD_SPI_NOR_WP_KEEP
>> +       bool "Keep write protection as is"
>> +       help
>> +         If you select this option the write protection of any SPI 
>> flashes
>> +         will not be changed. If your flash is write protected or 
>> will be
>> +         automatically write protected after power-up you have to 
>> manually
>> +         unlock it before you are able to write to it.
>> +
>> +endchoice
>> +
>>  source "drivers/mtd/spi-nor/controllers/Kconfig"
>> 
>>  endif # MTD_SPI_NOR
>> diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
>> index fe6a4653823d..215df7c4272b 100644
>> --- a/drivers/mtd/spi-nor/atmel.c
>> +++ b/drivers/mtd/spi-nor/atmel.c
>> @@ -8,6 +8,8 @@
>> 
>>  #include "core.h"
>> 
>> +#define ATMEL_SR_GLOBAL_PROTECT_MASK GENMASK(5, 2)
>> +
>>  /*
>>   * The Atmel AT25FS010/AT25FS040 parts have some weird configuration 
>> for the
>>   * block protection bits. We don't support them. But legacy behaviour 
>> in linux
>> @@ -55,6 +57,103 @@ static const struct spi_nor_fixups 
>> atmel_at25fs_fixups = {
>>         .default_init = atmel_at25fs_default_init,
>>  };
>> 
>> +/**
>> + * atmel_set_global_protection - Do a Global Protect or Unprotect 
>> command
>> + * @nor:       pointer to 'struct spi_nor'
>> + * @ofs:       offset in bytes
>> + * @len:       len in bytes
>> + * @is_protect:        if true do a Global Protect otherwise it is a 
>> Global Unprotect
>> + *
>> + * Return: 0 on success, -error otherwise.
>> + */
>> +static int atmel_set_global_protection(struct spi_nor *nor, loff_t 
>> ofs,
>> +                                      uint64_t len, bool is_protect)
>> +{
>> +       int ret;
>> +       u8 sr;
>> +
>> +       /* We only support locking the whole flash array */
>> +       if (ofs || len != nor->params->size)
>> +               return -EINVAL;
>> +
>> +       ret = spi_nor_read_sr(nor, nor->bouncebuf);
>> +       if (ret)
>> +               return ret;
> 
> maybe a new line in between.

ok

>> +       sr = nor->bouncebuf[0];
>> +
>> +       /* SRWD bit needs to be cleared, otherwise the protection 
>> doesn't change */
>> +       if (sr & SR_SRWD) {
>> +               sr &= ~SR_SRWD;
>> +               ret = spi_nor_write_sr_and_check(nor, sr);
>> +               if (ret) {
>> +                       dev_err(nor->dev, "unable to clear SRWD bit, 
>> WP# asserted?\n");
> 
> spi_nor_write_sr_and_check() already prints a dev_dbg(). If you find a
> second message
> useful, you should use dev_dbg for low level info.

The intention for this was to make the user aware the reason why the 
unlock
might not work. The reason I chose dev_err() was that its unlikely that 
John
Doe will have debug enabled. But I already came up with another reason 
why
this is bad: Everytime the kernel will start an unlock all might raise 
that
error. Therefore, I guess the kernel is the wrong place for that.

> 
>> +                       return ret;
>> +               }
>> +       }
>> +
>> +       if (is_protect) {
>> +               sr |= ATMEL_SR_GLOBAL_PROTECT_MASK;
>> +               /*
>> +                * Set the SRWD bit again as soon as we are protecting
>> +                * anything. This will ensure that the WP# pin is 
>> working
>> +                * correctly. By doing this we also behave the same as
>> +                * spi_nor_sr_lock(), which sets SRWD if any block 
>> protection
>> +                * is active.
>> +                */
>> +               sr |= SR_SRWD;
>> +       } else {
>> +               sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;
>> +       }
>> +
>> +       nor->bouncebuf[0] = sr;
>> +
>> +       /*
>> +        * We cannot use the spi_nor_write_sr_and_check() because this 
>> command
>> +        * isn't really setting any bits, instead it is an pseudo 
>> command for
>> +        * "Global Unprotect" or "Global Protect"
>> +        */
>> +       return spi_nor_write_sr(nor, nor->bouncebuf, 1);
>> +}
>> +
>> +static int atmel_global_protect(struct spi_nor *nor, loff_t ofs, 
>> uint64_t len)
>> +{
>> +       return atmel_set_global_protection(nor, ofs, len, true);
>> +}
>> +
>> +static int atmel_global_unprotect(struct spi_nor *nor, loff_t ofs, 
>> uint64_t len)
>> +{
>> +       return atmel_set_global_protection(nor, ofs, len, false);
>> +}
>> +
>> +static int atmel_is_global_protected(struct spi_nor *nor, loff_t ofs, 
>> uint64_t len)
>> +{
>> +       int ret;
>> +
>> +       if (ofs >= nor->params->size || (ofs + len) > 
>> nor->params->size)
>> +               return -EINVAL;
>> +
>> +       ret = spi_nor_read_sr(nor, nor->bouncebuf);
>> +       if (ret)
>> +               return ret;
>> +
>> +       return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == 
>> ATMEL_SR_GLOBAL_PROTECT_MASK);
>> +}
>> +
>> +static const struct spi_nor_locking_ops atmel_global_protection_ops = 
>> {
>> +       .lock = atmel_global_protect,
>> +       .unlock = atmel_global_unprotect,
>> +       .is_locked = atmel_is_global_protected,
>> +};
>> +
>> +static void atmel_global_protection_default_init(struct spi_nor *nor)
>> +{
>> +       nor->params->locking_ops = &atmel_global_protection_ops;
>> +}
>> +
>> +static const struct spi_nor_fixups atmel_global_protection_fixups = {
>> +       .default_init = atmel_global_protection_default_init,
>> +};
>> +
>>  static const struct flash_info atmel_parts[] = {
>>         /* Atmel -- some are (confusingly) marketed as "DataFlash" */
>>         { "at25fs010",  INFO(0x1f6601, 0, 32 * 1024,   4, SECT_4K | 
>> SPI_NOR_HAS_LOCK)
>> @@ -62,18 +161,32 @@ static const struct flash_info atmel_parts[] = {
>>         { "at25fs040",  INFO(0x1f6604, 0, 64 * 1024,   8, SECT_4K | 
>> SPI_NOR_HAS_LOCK)
>>                 .fixups = &atmel_at25fs_fixups },
>> 
>> -       { "at25df041a", INFO(0x1f4401, 0, 64 * 1024,   8, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> -       { "at25df321",  INFO(0x1f4700, 0, 64 * 1024,  64, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> -       { "at25df321a", INFO(0x1f4701, 0, 64 * 1024,  64, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> -       { "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> +       { "at25df041a", INFO(0x1f4401, 0, 64 * 1024,   8,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> +       { "at25df321",  INFO(0x1f4700, 0, 64 * 1024,  64,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> +       { "at25df321a", INFO(0x1f4701, 0, 64 * 1024,  64,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> +       { "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> 
>>         { "at25sl321",  INFO(0x1f4216, 0, 64 * 1024, 64,
>>                              SECT_4K | SPI_NOR_DUAL_READ | 
>> SPI_NOR_QUAD_READ) },
>> 
>>         { "at26f004",   INFO(0x1f0400, 0, 64 * 1024,  8, SECT_4K) },
>> -       { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> -       { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> -       { "at26df321",  INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K | 
>> SPI_NOR_HAS_LOCK) },
>> +       { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> +       { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> +       { "at26df321",  INFO(0x1f4700, 0, 64 * 1024, 64,
>> +                            SECT_4K | SPI_NOR_HAS_LOCK | 
>> SPI_NOR_WP_IS_VOLATILE)
>> +                       .fixups = &atmel_global_protection_fixups },
>> 
>>         { "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
>>  };
>> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
>> index 8c06a28a90de..8354ce0c8810 100644
>> --- a/drivers/mtd/spi-nor/core.c
>> +++ b/drivers/mtd/spi-nor/core.c
>> @@ -377,7 +377,7 @@ int spi_nor_write_disable(struct spi_nor *nor)
>>   *
>>   * Return: 0 on success, -errno otherwise.
>>   */
>> -static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
>> +int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
>>  {
>>         int ret;
>> 
>> @@ -1049,7 +1049,7 @@ static int 
>> spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
>>   *
>>   * Return: 0 on success, -errno otherwise.
>>   */
>> -static int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
>> +int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
>>  {
>>         if (nor->flags & SNOR_F_HAS_16BIT_SR)
>>                 return spi_nor_write_16bit_sr_and_check(nor, sr1);
>> @@ -3124,15 +3124,14 @@ static int spi_nor_quad_enable(struct spi_nor 
>> *nor)
>>   * spi_nor_unlock_all() - Unlocks the entire flash memory array.
>>   * @nor:       pointer to a 'struct spi_nor'.
>>   *
>> - * Some SPI NOR flashes are write protected by default after a 
>> power-on reset
>> - * cycle, in order to avoid inadvertent writes during power-up. 
>> Backward
>> - * compatibility imposes to unlock the entire flash memory array at 
>> power-up
>> - * by default.
>> + * Return: 0 on success, -errno otherwise.
>>   */
>>  static int spi_nor_unlock_all(struct spi_nor *nor)
>>  {
>> -       if (nor->flags & SNOR_F_HAS_LOCK)
>> +       if (nor->flags & SNOR_F_HAS_LOCK) {
>> +               dev_dbg(nor->dev, "unprotecting entire flash\n");
>>                 return spi_nor_unlock(&nor->mtd, 0, 
>> nor->params->size);
>> +       }
>> 
>>         return 0;
>>  }
>> @@ -3153,10 +3152,23 @@ static int spi_nor_init(struct spi_nor *nor)
>>                 return err;
>>         }
>> 
>> -       err = spi_nor_unlock_all(nor);
>> -       if (err) {
>> -               dev_dbg(nor->dev, "Failed to unlock the entire flash 
>> memory array\n");
>> -               return err;
>> +       /*
>> +        * Some SPI NOR flashes are write protected by default after a 
>> power-on
>> +        * reset cycle, in order to avoid inadvertent writes during 
>> power-up.
>> +        * Backward compatibility imposes to unlock the entire flash 
>> memory
>> +        * array at power-up by default. Depending on the kernel 
>> configuration
>> +        * (1) we do nothing, (2) we unlock the entire flash in any 
>> case or (3)
>> +        * just do it actually powers up write-protected. The latter 
>> is
> 
> do it if it actually powers up
> 
> How about: (1) do nothing, (2) always unlock the entire flash array, 
> (3) unlock
> the entire flash array only when the software protection bits are 
> volatile.

ok

>> +        * indicated by SNOR_F_WP_IS_VOLATILE.
>> +        */
>> +       if (IS_ENABLED(CONFIG_MTD_SPI_NOR_WP_DISABLE) ||
>> +           (IS_ENABLED(CONFIG_MTD_SPI_NOR_WP_DISABLE_ON_VOLATILE) &&
>> +            nor->flags & SNOR_F_WP_IS_VOLATILE)) {
>> +               err = spi_nor_unlock_all(nor);
>> +               if (err) {
>> +                       dev_err(nor->dev, "Failed to unlock the entire 
>> flash memory array\n");
> 
> dev_dbg for low level info

Is this low level info or an actual error? Which raises the question:
should spi_nor_unlock_all() in case SWRD couldn't be cleared and thus
should all the spi_nor_init fail of this? Or should it rather be a
soft error?

Also I don't know how spi_nor_sr_unlock() will behave.

>> +                       return err;
>> +               }
>>         }
>> 
[..]

-michael



More information about the linux-mtd mailing list