Re: [PATCH 1/3] mtd: spi-nor: reimplement block protection handling

Jungseung Lee
2020-03-14T13:50:46Z

Hi, chenxiang,

On Sat, Mar 14, 2020 at 6:58 PM chenxiang (M) <chenxiang66@hisilicon.com> wrote:
>
> Hi Jungseung,
>
> 在 2020/3/9 19:44, Jungseung Lee 写道:
> > Hi,
> >
> > 2020-03-09 (월), 15:50 +0800, chenxiang (M):
> >> Hi Jungseung,
> >>
> >> 在 2020/3/7 16:24, Jungseung Lee 写道:
> >>> Hi,
> >>>
> >>> 2020-03-06 (금), 20:19 +0800, chenxiang (M):
> >>>> Hi Jungseung,
> >>>>
> >>>> 在 2020/3/4 19:07, Jungseung Lee 写道:
> >>>>> The current mainline locking was restricted and could only be
> >>>>> applied
> >>>>> to flashes that has 3 block protection bit and fixed locking
> >>>>> ratio.
> >>>>>
> >>>>> A new method of normalization was reached at the end of the
> >>>>> discussion [1].
> >>>>>
> >>>>> (1) - if bp slot is insufficient.
> >>>>> (2) - if bp slot is sufficient.
> >>>>>
> >>>>> if (bp_slots_needed > bp_slots) // (1)
> >>>>> min_prot_length = sector_size << (bp_slots_needed -
> >>>>> bp_slots);
> >>>>> else // (2)
> >>>>> min_prot_length = sector_size;
> >>>>>
> >>>>> This patch changes block protection handling logic based on
> >>>>> min_prot_length.
> >>>>> It is suitable for the overall flashes with exception of some
> >>>>> corner case
> >>>>> and easy to extend and apply for the case of 2bit or 4bit block
> >>>>> protection.
> >>>>>
> >>>>> [1]
> >>>>>
> > https://protect2.fireeye.com/url?k=e80b1f1a-b5db17f2-e80a9455-000babff32e3-dadc30d1176f6374&u=http://lists.infradead.org/pipermail/linux-mtd/2020-February/093934.html
> >>>>
> >>>> I have tested the patchset on one of my board (there is micron
> >>>> flash
> >>>> n25q128a11 which supports 4bit BP, and also bp3 is on bit6 of SR,
> >>>> TB
> >>>> bit is on bit5 of SR), so
> >>>> i modify the code as follows to enable the lock/unlock of
> >>>> n25q128a11.
> >>>> - { "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256,
> >>>> SECT_4K |
> >>>> SPI_NOR_QUAD_READ) },
> >>>> + { "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256,
> >>>> + SECT_4K | SPI_NOR_QUAD_READ |
> >>>> + USE_FSR | SPI_NOR_HAS_LOCK |
> >>>> SPI_NOR_HAS_TB |
> >>>> + SPI_NOR_HAS_BP3 | SPI_NOR_BP3_SR_BIT6) },
> >>>>
> >>>> There are two issues i met:
> >>>> (1) i lock/unlock the full region of the flash, lock is valid,
> >>>> but
> >>>> there is error when unlock the flash, i query the status of it is
> >>>> unlock (the issue i think it is
> >>>> the same as the issue John has reported before
> >>>>
> > https://lore.kernel.org/linux-mtd/c1a92c89-020d-0847-b7bf-41dbfd9b972e@microchip.com/
> >>>> ),
> >>>>
> >>>> i screenshot the log of the operation as follows:
> >>>>
> >>> Looks like the unlock operation was actually done (as can be
> >>> checked
> >>> from the following query of the status) but an error is coming with
> >>> EIO.
> >>>
> >>> I think another part of sr handling is related with your case.
> >>> (maybe
> >>> SR read back test ?)
> >> Yes, it is the issue of SR read back test: it writes 0X2 (bit WEL
> >> is
> >> set), but it reads back 0x0 (bit WEL is cleared).
> >>
> > I am reviewing tudor's patches and it seems solve your issue
> > effectively.
> > http://lists.infradead.org/pipermail/linux-mtd/2020-March/094231.html
>
> Yes, it solves my issue.
>
> >
> >>> If you can dump the sr value & dev_dbg msg, it will be helpful to
> >>> define this issue.
> >>>
> >>>> (2) i try to lock part of the flash region such as ./flash_lock
> >>>> /dev/mtd0 0xc00000 10, it reports invalid argument,
> >>>> and i am not sure whether it is something wrong with my
> >>>> operation.
> >>>>
> >>> It is unable to lock such region since the spi flash doesn't
> >>> support
> >>> it. only we can lock it from the top or the bottom.
> >>>
> >>> like this for n25q128a11,
> >>>
> >>> flash_lock /dev/mtd0 0xff0000 0x10
> >>> flash_lock /dev/mtd0 0x0 0x10
> >> Do you mean if lock/unlcok from top, the address of lock/unlock
> >> commands should be the address of 255th block (0xff0000), 254th
> >> block(0xfe0000), 252nd block(0xfc0000), ...., 128th block (0x800000)?
> >> If lock/unlock from bottom, the address of lock/unlock commands
> >> should
> >> be always the address of 0th block (0x0)?
> >>
> > I'm not fully understanding the usage of flash_lock, but it would be
> > better to use such addresses for lock/unlocking to make it under
> > control.
> >
> > There are some ambiguous parts to explain that since some lock/unlock
> > operation is still working well without the addresses.
> >
> > LOCK
> > - Return success if the requested area is already locked.
> > - If requested area is not fully matched with lock portion of the
> > flash, lock some of the portion including the request area as it could
> > be.
> >
> > UNLOCK
> > - Return success if the requested area is already unlocked.
> > - If requested area is not fully matched with lock portion of the
> > flash, unlock all locked portion including the request area. the
> > portion would be bigger than requested area.
>
> Thanks for you info.
> I have tested above situations of lock and unlock, and still have a
> question about it:
> For unlock function, as you said, it will unlock all the locked portion
> including the request area which would be bigger than requested area if
> requested area is not fully matched with lock portion of the flash.
> But for lock function, it seem not lock some of portion including the
> request area as it could be, and it seems require the total locked area
> must be matched with
> some portion of the flash (it seems not allow hole between those regions).
>

Yes it is. The spi flash consequently controls the region that will be
locked through only one bp value on sr register.
I wrote only some of the patterns I checked in the current mainline
code, and frankly, I don't know if even this is always right in all
combinations.

Thanks,

> For example, 16MB in my envirnment, i do as follows:
> - lock [0xff0000, 0x1000000] which is the 255th block -> it is matched
> with lock portion of the flash (BP3~0 = 0001, TB=0)
> - lock [0xc00000, 0xff0000] or [0xc00000, 0xff1000] -> it also matched
> with lock portion of the flash (BP3~0 = 0111, TB=0)
> but if do it as follows:
> - lock [0xff0000, 0x1000000] which is the 255th block -> it is matched
> with lock portion of the flash (BP3~0 = 0001, TB=0)
> - lock [0xc00000, 0xc10000] -> it will report invalid argument at the
> second time, in my thought it would lock [0xc00000, 0x1000000] which
> will including those two regions
>
> >
> > So, the lock/unlock would be able to work without the addresses. but in
> > my case I don't use the way because it will makes hard to tracking the
> > locked area.
> >
> > Thanks,
> >
> >>> Note the block count of examples is 0x10 not 10. The locking try
> >>> with
> >>> block count under minimum block protection length will be failed.
> >>>
> >>> Thanks,
> >>>
> >>>>> Signed-off-by: Jungseung Lee <js07.lee@samsung.com>
> >>>>> ---
> >>>>> drivers/mtd/spi-nor/spi-nor.c | 110 ++++++++++++++++++++-----
> >>>>> ---
> >>>>> ------
> >>>>> 1 file changed, 66 insertions(+), 44 deletions(-)
> >>>>>
> >>>>> diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-
> >>>>> nor/spi-nor.c
> >>>>> index caf0c109cca0..c58c27552a74 100644
> >>>>> --- a/drivers/mtd/spi-nor/spi-nor.c
> >>>>> +++ b/drivers/mtd/spi-nor/spi-nor.c
> >>>>> @@ -1784,29 +1784,64 @@ static int spi_nor_erase(struct
> >>>>> mtd_info
> >>>>> *mtd, struct erase_info *instr)
> >>>>> return ret;
> >>>>> }
> >>>>>
> >>>>> +static u8 spi_nor_get_bp_mask(struct spi_nor *nor)
> >>>>> +{
> >>>>> + return SR_BP2 | SR_BP1 | SR_BP0;
> >>>>> +}
> >>>>> +
> >>>>> +static u8 spi_nor_get_tb_mask(struct spi_nor *nor)
> >>>>> +{
> >>>>> + if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
> >>>>> + return SR_TB_BIT6;
> >>>>> + else
> >>>>> + return SR_TB_BIT5;
> >>>>> +}
> >>>>> +
> >>>>> +static int stm_get_min_prot_length(struct spi_nor *nor)
> >>>>> +{
> >>>>> + int bp_slots, bp_slots_needed;
> >>>>> + u8 mask = spi_nor_get_bp_mask(nor);
> >>>>> +
> >>>>> + bp_slots = (mask >> SR_BP_SHIFT) + 1;
> >>>>> +
> >>>>> + /* Reserved one for "protect none" and one for "protect
> >>>>> all".
> >>>>> */
> >>>>> + bp_slots = bp_slots - 2;
> >>>>> +
> >>>>> + bp_slots_needed = ilog2(nor->info->n_sectors);
> >>>>> +
> >>>>> + if (bp_slots_needed > bp_slots)
> >>>>> + return nor->info->sector_size <<
> >>>>> + (bp_slots_needed - bp_slots);
> >>>>> + else
> >>>>> + return nor->info->sector_size;
> >>>>> +}
> >>>>> +
> >>>>> 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;
> >>>>> - u8 tb_mask = SR_TB_BIT5;
> >>>>> - int pow;
> >>>>> + int min_prot_len;
> >>>>> + u8 mask = spi_nor_get_bp_mask(nor);
> >>>>> + u8 tb_mask = spi_nor_get_tb_mask(nor);
> >>>>> + u8 bp = (sr & mask) >> SR_BP_SHIFT;
> >>>>>
> >>>>> - if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
> >>>>> - tb_mask = SR_TB_BIT6;
> >>>>> -
> >>>>> - if (!(sr & mask)) {
> >>>>> + if (!bp) {
> >>>>> /* No protection */
> >>>>> *ofs = 0;
> >>>>> *len = 0;
> >>>>> - } else {
> >>>>> - pow = ((sr & mask) ^ mask) >> SR_BP_SHIFT;
> >>>>> - *len = mtd->size >> pow;
> >>>>> - if (nor->flags & SNOR_F_HAS_SR_TB && sr &
> >>>>> tb_mask)
> >>>>> - *ofs = 0;
> >>>>> - else
> >>>>> - *ofs = mtd->size - *len;
> >>>>> + return;
> >>>>> }
> >>>>> +
> >>>>> + min_prot_len = stm_get_min_prot_length(nor);
> >>>>> + *len = min_prot_len << (bp - 1);
> >>>>> +
> >>>>> + if (*len > mtd->size)
> >>>>> + *len = mtd->size;
> >>>>> +
> >>>>> + if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
> >>>>> + *ofs = 0;
> >>>>> + else
> >>>>> + *ofs = mtd->size - *len;
> >>>>> }
> >>>>>
> >>>>> /*
> >>>>> @@ -1880,8 +1915,9 @@ static int stm_lock(struct spi_nor *nor,
> >>>>> loff_t ofs, uint64_t len)
> >>>>> {
> >>>>> struct mtd_info *mtd = &nor->mtd;
> >>>>> int ret, status_old, status_new;
> >>>>> - u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
> >>>>> - u8 tb_mask = SR_TB_BIT5;
> >>>>> + int min_prot_len;
> >>>>> + u8 mask = spi_nor_get_bp_mask(nor);
> >>>>> + u8 tb_mask = spi_nor_get_tb_mask(nor);
> >>>>> u8 pow, val;
> >>>>> loff_t lock_len;
> >>>>> bool can_be_top = true, can_be_bottom = nor->flags &
> >>>>> SNOR_F_HAS_SR_TB;
> >>>>> @@ -1918,20 +1954,14 @@ static int stm_lock(struct spi_nor
> >>>>> *nor,
> >>>>> loff_t ofs, uint64_t len)
> >>>>> else
> >>>>> lock_len = ofs + len;
> >>>>>
> >>>>> - if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
> >>>>> - tb_mask = SR_TB_BIT6;
> >>>>> + if (lock_len == mtd->size) {
> >>>>> + val = mask; /* fully locked */
> >>>>> + } else {
> >>>>> + min_prot_len = stm_get_min_prot_length(nor);
> >>>>> + pow = ilog2(lock_len) - ilog2(min_prot_len) +
> >>>>> 1;
> >>>>> + val = pow << SR_BP_SHIFT;
> >>>>> + }
> >>>>>
> >>>>> - /*
> >>>>> - * 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(mtd->size) - ilog2(lock_len);
> >>>>> - val = mask - (pow << SR_BP_SHIFT);
> >>>>> if (val & ~mask)
> >>>>> return -EINVAL;
> >>>>> /* Don't "lock" with no region! */
> >>>>> @@ -1966,8 +1996,9 @@ static int stm_unlock(struct spi_nor
> >>>>> *nor,
> >>>>> loff_t ofs, uint64_t len)
> >>>>> {
> >>>>> struct mtd_info *mtd = &nor->mtd;
> >>>>> int ret, status_old, status_new;
> >>>>> - u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
> >>>>> - u8 tb_mask = SR_TB_BIT5;
> >>>>> + int min_prot_len;
> >>>>> + u8 mask = spi_nor_get_bp_mask(nor);
> >>>>> + u8 tb_mask = spi_nor_get_tb_mask(nor);
> >>>>> u8 pow, val;
> >>>>> loff_t lock_len;
> >>>>> bool can_be_top = true, can_be_bottom = nor->flags &
> >>>>> SNOR_F_HAS_SR_TB;
> >>>>> @@ -2004,22 +2035,13 @@ static int stm_unlock(struct spi_nor
> >>>>> *nor,
> >>>>> loff_t ofs, uint64_t len)
> >>>>> else
> >>>>> lock_len = ofs;
> >>>>>
> >>>>> - if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
> >>>>> - tb_mask = SR_TB_BIT6;
> >>>>> - /*
> >>>>> - * 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(mtd->size) - order_base_2(lock_len);
> >>>>> if (lock_len == 0) {
> >>>>> val = 0; /* fully unlocked */
> >>>>> } else {
> >>>>> - val = mask - (pow << SR_BP_SHIFT);
> >>>>> + min_prot_len = stm_get_min_prot_length(nor);
> >>>>> + pow = ilog2(lock_len) - ilog2(min_prot_len) +
> >>>>> 1;
> >>>>> + val = pow << SR_BP_SHIFT;
> >>>>> +
> >>>>> /* Some power-of-two sizes are not supported */
> >>>>> if (val & ~mask)
> >>>>> return -EINVAL;
> >>>>
> >>> .
> >>>
> >>
> >>
> >
> > .
> >