* Re: [PATCH 1/2] ubi: build: replace simple_strtoul with kstrtouint in open_mtd_device()
From: Zhihao Cheng @ 2026-05-07 7:40 UTC (permalink / raw)
To: haoyu.lu, Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel
In-Reply-To: <20260507032308.16485-1-hechushiguitu666@gmail.com>
在 2026/5/7 11:23, haoyu.lu 写道:
> From: Haoyu Lu <hechushiguitu666@gmail.com>
>
> Replace the deprecated simple_strtoul() with kstrtouint() for parsing
> the MTD device number string. kstrtouint() provides stricter validation
> and better error handling.
>
> In open_mtd_device(), a string that can be parsed as a pure number is
> treated as an MTD device index; otherwise it is treated as a device
> name. kstrtouint() returns 0 on success and -EINVAL if the string
> contains non-numeric characters, which aligns with this logic and
> eliminates the need for manual endp checks.
>
> For compatibility with kstrtouint(), mtd_num is changed from int to
> unsigned int, consistent with the existing simple_strtoul ->
> kstrtouint conversions in mtdoops.c and mtdsuper.c.
>
> The multi-line comment is simplified to a concise single-line comment
> since the logic is self-explanatory with kstrtouint().
>
> Signed-off-by: Haoyu Lu <hechushiguitu666@gmail.com>
> ---
> drivers/mtd/ubi/build.c | 15 +++++----------
> 1 file changed, 5 insertions(+), 10 deletions(-)
>
Reviewed-by: Zhihao Cheng <chengzhihao1@huawei.com>
> diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
> index 674ad87809df..03ef195dc25c 100644
> --- a/drivers/mtd/ubi/build.c
> +++ b/drivers/mtd/ubi/build.c
> @@ -1210,21 +1210,16 @@ static struct mtd_info * __init open_mtd_by_chdev(const char *mtd_dev)
> static struct mtd_info * __init open_mtd_device(const char *mtd_dev)
> {
> struct mtd_info *mtd;
> - int mtd_num;
> - char *endp;
> + unsigned int mtd_num;
>
> - mtd_num = simple_strtoul(mtd_dev, &endp, 0);
> - if (*endp != '\0' || mtd_dev == endp) {
> - /*
> - * This does not look like an ASCII integer, probably this is
> - * MTD device name.
> - */
> + if (kstrtouint(mtd_dev, 0, &mtd_num) != 0) {
> + /* Not a plain number, treat as MTD device name */
> mtd = get_mtd_device_nm(mtd_dev);
> if (PTR_ERR(mtd) == -ENODEV)
> - /* Probably this is an MTD character device node path */
> mtd = open_mtd_by_chdev(mtd_dev);
> - } else
> + } else {
> mtd = get_mtd_device(NULL, mtd_num);
> + }
>
> return mtd;
> }
>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply
* Re: [PATCH 2/2] ubi: build: replace simple_strtoul with kstrtoul in bytes_str_to_int()
From: Zhihao Cheng @ 2026-05-07 7:45 UTC (permalink / raw)
To: haoyu.lu, Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel
In-Reply-To: <20260507032308.16485-2-hechushiguitu666@gmail.com>
在 2026/5/7 11:23, haoyu.lu 写道:
> From: Haoyu Lu <hechushiguitu666@gmail.com>
>
> Replace the deprecated simple_strtoul() with kstrtoul() in the
> bytes_str_to_int() helper function. Since kstrtoul() rejects trailing
> non-numeric characters (such as the G/M/K suffixes), the numeric prefix
> is first extracted with strspn() and then parsed separately before
> handling the suffix.
>
> This provides proper error handling through the kstrto* family while
> preserving the existing suffix semantics for byte count parameters.
>
> Note: the original simple_strtoul() with base=0 accepted hexadecimal
> (0x prefix) and octal (0 prefix) formats, while kstrtoul() with base=10
> only supports decimal. This is not a practical concern since MTD byte
> count parameters are always specified as decimal values in boot
> parameters.
The ubi could be loaded by module insertion, many vendors set param
'vid_hdr_offs' to save space. We cannot make the assumption that all
users use the decimal system.
>
> Signed-off-by: Haoyu Lu <hechushiguitu666@gmail.com>
> ---
> drivers/mtd/ubi/build.c | 19 +++++++++++++++----
> 1 file changed, 15 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
> index 03ef195dc25c..7cb6ba4a3840 100644
> --- a/drivers/mtd/ubi/build.c
> +++ b/drivers/mtd/ubi/build.c
> @@ -1426,16 +1426,27 @@ module_exit(ubi_exit);
> */
> static int bytes_str_to_int(const char *str)
> {
> - char *endp;
> unsigned long result;
> + unsigned int num_len;
> + char num_buf[32];
>
> - result = simple_strtoul(str, &endp, 0);
> - if (str == endp || result >= INT_MAX) {
> + /* Find the length of the numeric prefix */
> + num_len = strspn(str, "0123456789");
> + if (num_len == 0 || num_len >= sizeof(num_buf)) {
> pr_err("UBI error: incorrect bytes count: \"%s\"\n", str);
> return -EINVAL;
> }
>
> - switch (*endp) {
> + /* Parse the numeric part */
> + memcpy(num_buf, str, num_len);
> + num_buf[num_len] = '\0';
> + if (kstrtoul(num_buf, 10, &result) < 0 || result >= INT_MAX) {
> + pr_err("UBI error: incorrect bytes count: \"%s\"\n", str);
> + return -EINVAL;
> + }
> +
> + /* Handle suffix */
> + switch (str[num_len]) {
> case 'G':
> result *= 1024;
> fallthrough;
>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply
* Re: [PATCH 2/2] ubi: build: replace simple_strtoul with kstrtoul in bytes_str_to_int()
From: Richard Weinberger @ 2026-05-07 7:50 UTC (permalink / raw)
To: chengzhihao1
Cc: haoyu.lu, Miquel Raynal, Vignesh Raghavendra, linux-mtd,
linux-kernel
In-Reply-To: <7c4bf8d4-c561-f5bf-fc7f-3e384fe1a9e6@huawei.com>
----- Ursprüngliche Mail -----
> Von: "chengzhihao1" <chengzhihao1@huawei.com>
> An: "haoyu.lu" <hechushiguitu666@gmail.com>, "Miquel Raynal" <miquel.raynal@bootlin.com>, "richard" <richard@nod.at>,
> "Vignesh Raghavendra" <vigneshr@ti.com>
> CC: "linux-mtd" <linux-mtd@lists.infradead.org>, "linux-kernel" <linux-kernel@vger.kernel.org>
> Gesendet: Donnerstag, 7. Mai 2026 09:45:24
> Betreff: Re: [PATCH 2/2] ubi: build: replace simple_strtoul with kstrtoul in bytes_str_to_int()
> 在 2026/5/7 11:23, haoyu.lu 写道:
>> From: Haoyu Lu <hechushiguitu666@gmail.com>
>>
>> Replace the deprecated simple_strtoul() with kstrtoul() in the
>> bytes_str_to_int() helper function. Since kstrtoul() rejects trailing
>> non-numeric characters (such as the G/M/K suffixes), the numeric prefix
>> is first extracted with strspn() and then parsed separately before
>> handling the suffix.
>>
>> This provides proper error handling through the kstrto* family while
>> preserving the existing suffix semantics for byte count parameters.
>>
>> Note: the original simple_strtoul() with base=0 accepted hexadecimal
>> (0x prefix) and octal (0 prefix) formats, while kstrtoul() with base=10
>> only supports decimal. This is not a practical concern since MTD byte
>> count parameters are always specified as decimal values in boot
>> parameters.
> The ubi could be loaded by module insertion, many vendors set param
> 'vid_hdr_offs' to save space. We cannot make the assumption that all
> users use the decimal system.
That's a good point!
In general I'm not fond of such changes. They change code for the sake
of changing code without fixing actual issues.
I'll happily accept patches that point out real issues with the current
usage of various string functions, though.
Thanks,
//richard
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply
* Re: [PATCH 2/2] ubi: build: replace simple_strtoul with kstrtoul in bytes_str_to_int()
From: Haoyu Lu @ 2026-05-07 8:25 UTC (permalink / raw)
To: Richard Weinberger
Cc: chengzhihao1, Miquel Raynal, Vignesh Raghavendra, linux-mtd,
linux-kernel
In-Reply-To: <874282979.1642.1778140250921.JavaMail.zimbra@nod.at>
Hi Richard, Zhihao,
Thanks for the feedback. I will drop this patch.
Thanks,
Haoyu
On Thu, May 7, 2026 at 3:50 PM Richard Weinberger <richard@nod.at> wrote:
>
> ----- Ursprüngliche Mail -----
> > Von: "chengzhihao1" <chengzhihao1@huawei.com>
> > An: "haoyu.lu" <hechushiguitu666@gmail.com>, "Miquel Raynal" <miquel.raynal@bootlin.com>, "richard" <richard@nod.at>,
> > "Vignesh Raghavendra" <vigneshr@ti.com>
> > CC: "linux-mtd" <linux-mtd@lists.infradead.org>, "linux-kernel" <linux-kernel@vger.kernel.org>
> > Gesendet: Donnerstag, 7. Mai 2026 09:45:24
> > Betreff: Re: [PATCH 2/2] ubi: build: replace simple_strtoul with kstrtoul in bytes_str_to_int()
>
> > 在 2026/5/7 11:23, haoyu.lu 写道:
> >> From: Haoyu Lu <hechushiguitu666@gmail.com>
> >>
> >> Replace the deprecated simple_strtoul() with kstrtoul() in the
> >> bytes_str_to_int() helper function. Since kstrtoul() rejects trailing
> >> non-numeric characters (such as the G/M/K suffixes), the numeric prefix
> >> is first extracted with strspn() and then parsed separately before
> >> handling the suffix.
> >>
> >> This provides proper error handling through the kstrto* family while
> >> preserving the existing suffix semantics for byte count parameters.
> >>
> >> Note: the original simple_strtoul() with base=0 accepted hexadecimal
> >> (0x prefix) and octal (0 prefix) formats, while kstrtoul() with base=10
> >> only supports decimal. This is not a practical concern since MTD byte
> >> count parameters are always specified as decimal values in boot
> >> parameters.
> > The ubi could be loaded by module insertion, many vendors set param
> > 'vid_hdr_offs' to save space. We cannot make the assumption that all
> > users use the decimal system.
>
> That's a good point!
>
> In general I'm not fond of such changes. They change code for the sake
> of changing code without fixing actual issues.
> I'll happily accept patches that point out real issues with the current
> usage of various string functions, though.
>
> Thanks,
> //richard
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply
* [PATCH v5 05/28] mtd: spi-nor: Improve opcodes documentation
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
There are two status registers, named 1 and 2. The current wording is
misleading as "1" may refer to the status register ID as well as the
number of bytes required (which, in this case can be 1 or 2).
Clarify the comments by aligning them on the same pattern:
"{read,write} status {1,2} register"
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
include/linux/mtd/spi-nor.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index cdcfe0fd2e7d..90a0cf583512 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -21,8 +21,8 @@
/* Flash opcodes. */
#define SPINOR_OP_WRDI 0x04 /* Write disable */
#define SPINOR_OP_WREN 0x06 /* Write enable */
-#define SPINOR_OP_RDSR 0x05 /* Read status register */
-#define SPINOR_OP_WRSR 0x01 /* Write status register 1 byte */
+#define SPINOR_OP_RDSR 0x05 /* Read status register 1 */
+#define SPINOR_OP_WRSR 0x01 /* Write status register 1 */
#define SPINOR_OP_RDSR2 0x3f /* Read status register 2 */
#define SPINOR_OP_WRSR2 0x3e /* Write status register 2 */
#define SPINOR_OP_READ 0x03 /* Read data bytes (low frequency) */
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 03/28] mtd: spi-nor: Make sure the QE bit is kept enabled if useful
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
Not all chips implement the 4BAIT table which typically indicates the
program capability, while many of them do implement the relevant SFDP
parts indicating the read capabilities. In such a situation, programs
can happen in single mode (1-1-1) and reads in quad mode (1-1-4 or
1-4-4). For the reads to work in such condition, the QE bit must be set.
In case we later use the spi_nor_write_16bit_sr_and_check() helper with
a chip with such configuration, the QE bit would get incorrectly
cleared.
Make sure this doesn't happen by keeping the QE bit under a simpler
condition:
- the quad enable hook is there (no change)
- and at least one of the two protocols is based on quad I/O cycles
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 5dd0b3cb5250..394c27de02d6 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -869,8 +869,8 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
ret = spi_nor_read_cr(nor, &sr_cr[1]);
if (ret)
return ret;
- } else if (spi_nor_get_protocol_width(nor->read_proto) == 4 &&
- spi_nor_get_protocol_width(nor->write_proto) == 4 &&
+ } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+ spi_nor_get_protocol_width(nor->write_proto) == 4) &&
nor->params->quad_enable) {
/*
* If the Status Register 2 Read command (35h) is not
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 01/28] mtd: spi-nor: Drop duplicate Kconfig dependency
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
I do not think the MTD dependency is needed twice. This is likely a
duplicate coming from a former rebase when the spi-nor core got cleaned
up a while ago. Remove the extra line.
Fixes: b35b9a10362d ("mtd: spi-nor: Move m25p80 code in spi-nor.c")
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
---
drivers/mtd/spi-nor/Kconfig | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 24cd25de2b8b..fd05a24d64a9 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig MTD_SPI_NOR
tristate "SPI NOR device support"
- depends on MTD
depends on MTD && SPI_MASTER
select SPI_MEM
help
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 06/28] mtd: spi-nor: debugfs: Align variable access with the rest of the file
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
The "params" variable is used everywhere else, align this particular
line of the file to use "params" directly rather than the "nor" pointer.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/debugfs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index d700e0b27182..69830ad43990 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -139,7 +139,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
- seq_printf(s, " %02x (%s)\n", nor->params->die_erase_opcode, buf);
+ seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf);
}
seq_puts(s, "\nsector map\n");
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 07/28] mtd: spi-nor: debugfs: Enhance output
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
Align the number of dashes to the bigger column width (the title in this
case) to make the output more pleasant and aligned with what is done
in the "params" file output.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/debugfs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 69830ad43990..d0191eb9f879 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -144,7 +144,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
seq_puts(s, "\nsector map\n");
seq_puts(s, " region (in hex) | erase mask | overlaid\n");
- seq_puts(s, " ------------------+------------+----------\n");
+ seq_puts(s, " ------------------+------------+---------\n");
for (i = 0; i < erase_map->n_regions; i++) {
u64 start = region[i].offset;
u64 end = start + region[i].size - 1;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 02/28] mtd: spi-nor: debugfs: Fix the flags list
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
As mentioned above the spi_nor_option_flags enumeration in core.h, this
list should be kept in sync with the one in the core.
Add the missing flag.
Fixes: 6a42bc97ccda ("mtd: spi-nor: core: Allow specifying the byte order in Octal DTR mode")
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/debugfs.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index fa6956144d2e..d700e0b27182 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -28,6 +28,7 @@ static const char *const snor_f_names[] = {
SNOR_F_NAME(RWW),
SNOR_F_NAME(ECC),
SNOR_F_NAME(NO_WP),
+ SNOR_F_NAME(SWAP16),
};
#undef SNOR_F_NAME
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 04/28] mtd: spi-nor: swp: Improve locking user experience
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal, stable
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
In the case of the first block being locked (or the few first blocks),
if the user want to fully unlock the device it has two possibilities:
- either it asks to unlock the entire device, and this works;
- or it asks to unlock just the block(s) that are currently locked,
which fails.
It fails because the conditions "can_be_top" and "can_be_bottom" are
true. Indeed, in this case, we unlock everything, so the TB bit does not
matter. However in the current implementation, use_top would be true (as
this is the favourite option) and lock_len, which in practice should be
reduced down to 0, is set to "nor->params->size - (ofs + len)" which is
a positive number. This is wrong.
An easy way is to simply add an extra condition. In the unlock() path,
if we can achieve the same result from both sides, it means we unlock
everything and lock_len must simply be 0. A comment is added to clarify
that logic.
Fixes: 3dd8012a8eeb ("mtd: spi-nor: add TB (Top/Bottom) protect support")
Cc: stable@kernel.org
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
---
drivers/mtd/spi-nor/swp.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index e67a81dbb6bf..d5f4bf555cfc 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -282,8 +282,15 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
/* Prefer top, if both are valid */
use_top = can_be_top;
- /* lock_len: length of region that should remain locked */
- if (use_top)
+ /*
+ * lock_len: length of region that should remain locked.
+ *
+ * When can_be_top and can_be_bottom booleans are true, both adjacent
+ * regions are unlocked, thus the entire flash can be unlocked.
+ */
+ if (can_be_top && can_be_bottom)
+ lock_len = 0;
+ else if (use_top)
lock_len = nor->params->size - (ofs + len);
else
lock_len = ofs;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 00/28] mtd: spi-nor: Enhance software protection
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal, stable
Hello,
As recently raised on the mailing-list (link below), it seems that the
"locking" support in SPI NOR could benefit from some enhancements. As I
myself had to dig into it recently, here is a proposal.
First issue that I see, the MEMLOCK ioctl is not behaving correctly
in some cases, as addressed in:
mtd: spi-nor: swp: Improve locking user experience
Then there is no clear explanation of the shortcuts taken by the kernel
in terms of uAPI, so there is an attempt to list them in:
mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour
Plus, Tudor also asked if we could cover locking in the testing
procedure, which is done in:
mtd: spi-nor: Add steps for testing locking support
In order to simplify this procedure, and because it got very helpful
during my testing/development, I want to propose additions to the
debugfs output:
mtd: spi-nor: debugfs: Add locking support TODO: make the captures again
Finally, I am providing an implementation for the complement (CMP)
feature in order to allow finer control of the regions locked. This
feature is for instance available on Winbond chips:
[core] mtd: spi-nor: swp: Add support for the complement feature
[doc] mtd: spi-nor: Add steps for testing locking with CMP
[use] mtd: spi-nor: winbond: Add CMP locking support
Disclaimer: it was much less straightforward than I initially thought to
get the CMP feature working correctly. I tested it with as much focus as
I could, and I am improving the test coverage for the new cases, I am
also providing extra test cases in the metadata of the commit (which do
not make sense to test for chip additions, but may be sensible show when
making core additions like this one), but honestly there are so many
possibilities, I may still be missing corner cases. I hope this will
anyway be helpful to others!
All the other patches are misc improvements or style fixes which I faced
and fixed during my development.
Link: https://lore.kernel.org/linux-mtd/92e99a96-5582-48a5-a4f9-e9b33fcff171@linux.dev/
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
Changes in v5:
- Fixed the writing of SR2 in the locking ops for devices without RDCR
capability.
- Moved the ioctl comments to an existing comment as advised by
Pratyush.
- Changed the documentation to use the concept of lock sector instead of
block because on small density devices, the two amounts are typically
different.
- Renamed the debugfs output "#blocks" -> "#sectors" for the same
reason.
- Added a patch to make sure the QE bit is not badly overwritten when
using the core's SR/CR status write helpers.
- Link to v4: https://lore.kernel.org/r/20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com
Changes in v4:
- Make sure we don't try to show the (SR specific) debugfs info if the
chip does not support an SR based locking scheme. For this, add a new
helper to derive whether we are using the default ops or not.
- Fix compilation issue on arm32, by using div_u64.
- Link to v3: https://lore.kernel.org/r/20260317-winbond-v6-18-rc1-spi-nor-swp-v3-0-2ca9ea4e7b9b@bootlin.com
Changes in v3:
- No change at all, just rebased on top of v7.0-rc1.
- Collected 2 R-by from M. Walle.
- Link to v2: https://lore.kernel.org/r/20260108-winbond-v6-18-rc1-spi-nor-swp-v2-0-c462ef806130@bootlin.com
Changes in v2:
- Collect tags.
- Add missing Fixes/Cc: stable tags.
- Add a comment explaining why can_be_top && can_be_bottom is a specific
condition.
- Fix commit logs following Michael Walle's reviews.
- Amend the documentation following our discussion with Michael Walle as
well.
- Cache the SR register for debugfs use.
- Create a locked sector map file instead of dumping it as part of the
`params` file output.
- Improved greatly the output of the map as suggested by Michael.
- Add a patch fixing a duplicate dependency in Kconfig.
- Add an important comment in the doc about the small 4kiB erase size
choice.
- Add test runs for each and every chip for which the CMP feature is
added. This prove me that testing of each and every chip was needed,
as some of them seem to feature a broken BFPT table which does not
advertise a working 35h (Read CR) command.
- Added a condition on which the CMP feature is enabled: RDCR must be
possible.
- Link to v1: https://lore.kernel.org/r/20251114-winbond-v6-18-rc1-spi-nor-swp-v1-0-487bc7129931@bootlin.com
---
Miquel Raynal (28):
mtd: spi-nor: Drop duplicate Kconfig dependency
mtd: spi-nor: debugfs: Fix the flags list
mtd: spi-nor: Make sure the QE bit is kept enabled if useful
mtd: spi-nor: swp: Improve locking user experience
mtd: spi-nor: Improve opcodes documentation
mtd: spi-nor: debugfs: Align variable access with the rest of the file
mtd: spi-nor: debugfs: Enhance output
mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour
mtd: spi-nor: swp: Clarify a comment
mtd: spi-nor: swp: Use a pointer for SR instead of a single byte
mtd: spi-nor: swp: Create a helper that writes SR, CR and checks
mtd: spi-nor: swp: Rename a mask
mtd: spi-nor: swp: Create a TB intermediate variable
mtd: spi-nor: swp: Create helpers for building the SR register
mtd: spi-nor: swp: Simplify checking the locked/unlocked range
mtd: spi-nor: swp: Cosmetic changes
mtd: spi-nor: Create a local SR cache
mtd: spi-nor: debugfs: Add locking support
mtd: spi-nor: debugfs: Add a locked sectors map
mtd: spi-nor: Add steps for testing locking support
mtd: spi-nor: swp: Add support for the complement feature
mtd: spi-nor: Add steps for testing locking with CMP
mtd: spi-nor: winbond: Add W25H512NWxxAM CMP locking support
mtd: spi-nor: winbond: Add W25H01NWxxAM CMP locking support
mtd: spi-nor: winbond: Add W25H02NWxxAM CMP locking support
mtd: spi-nor: winbond: Add W25H01NWxxIQ CMP locking support
mtd: spi-nor: winbond: Add W25Q01NWxxIM CMP locking support
mtd: spi-nor: winbond: Add W25Q02NWxxIM CMP locking support
Documentation/driver-api/mtd/spi-nor.rst | 169 +++++++++++++++
drivers/mtd/spi-nor/Kconfig | 1 -
drivers/mtd/spi-nor/core.c | 78 ++++++-
drivers/mtd/spi-nor/core.h | 25 ++-
drivers/mtd/spi-nor/debugfs.c | 69 +++++-
drivers/mtd/spi-nor/swp.c | 359 ++++++++++++++++++++++++-------
drivers/mtd/spi-nor/winbond.c | 41 +++-
include/linux/mtd/spi-nor.h | 7 +-
8 files changed, 656 insertions(+), 93 deletions(-)
---
base-commit: 2df04c01437d44ea7cac641cb4d0c9fd1275df45
change-id: 20251114-winbond-v6-18-rc1-spi-nor-swp-865d36f4f695
Best regards,
--
Miquel Raynal <miquel.raynal@bootlin.com>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply
* [PATCH v5 09/28] mtd: spi-nor: swp: Clarify a comment
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
The comment states that some power of two sizes are not supported. This
is very device dependent (based on the size), so modulate a bit the
sentence to make it more accurate.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index d5f4bf555cfc..f221d6361b57 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -305,7 +305,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
val = (val & ~SR_BP3) | SR_BP3_BIT6;
- /* Some power-of-two sizes are not supported */
+ /* Some power-of-two sizes may not be supported */
if (val & ~mask)
return -EINVAL;
}
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 08/28] mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
Add more details about how these requests are actually handled in the
SPI NOR core. Their behaviour was not entirely clear to me at first, and
explaining them in plain English sounds the way to go.
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/core.h | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index e838c40a2589..22f21497c720 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -279,9 +279,14 @@ struct spi_nor_erase_map {
/**
* struct spi_nor_locking_ops - SPI NOR locking methods
- * @lock: lock a region of the SPI NOR.
- * @unlock: unlock a region of the SPI NOR.
- * @is_locked: check if a region of the SPI NOR is completely locked
+ * @lock: lock a region of the SPI NOR, never locks more than what is
+ * requested, ie. may lock less.
+ * @unlock: unlock a region of the SPI NOR, never unlocks more than what is
+ * requested, ie. may unlock less.
+ * @is_locked: check if a region of the SPI NOR is completely locked, returns
+ * false otherwise. This feeback may be misleading because users
+ * may get an "unlocked" status even though a subpart of the region
+ * is effectively locked.
*/
struct spi_nor_locking_ops {
int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 10/28] mtd: spi-nor: swp: Use a pointer for SR instead of a single byte
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
At this stage, the Status Register is most often seen as a single
byte. This is subject to change when we will need to read the CMP bit
which is located in the Control Register (kind of secondary status
register). Both will need to be carried.
Change a few prototypes to carry a u8 pointer. This way it also makes it
very clear where we access the first register, and where we will access
the second.
There is no functional change.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 48 ++++++++++++++++++++++++-----------------------
1 file changed, 25 insertions(+), 23 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index f221d6361b57..61830f18a147 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -55,13 +55,13 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
return sector_size;
}
-static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
+static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs,
u64 *len)
{
u64 min_prot_len;
u8 mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
- u8 bp, val = sr & mask;
+ u8 bp, val = sr[0] & mask;
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
val = (val & ~SR_BP3_BIT6) | SR_BP3;
@@ -81,7 +81,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
if (*len > nor->params->size)
*len = nor->params->size;
- if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
+ if (nor->flags & SNOR_F_HAS_SR_TB && sr[0] & tb_mask)
*ofs = 0;
else
*ofs = nor->params->size - *len;
@@ -92,7 +92,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
* (if @locked is false); false otherwise.
*/
static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
- u64 len, u8 sr, bool locked)
+ u64 len, const u8 *sr, bool locked)
{
loff_t lock_offs, lock_offs_max, offs_max;
u64 lock_len;
@@ -113,13 +113,13 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
return (ofs >= lock_offs_max) || (offs_max <= lock_offs);
}
-static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, u8 sr)
+static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr)
{
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
}
static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
- u8 sr)
+ const u8 *sr)
{
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
}
@@ -160,7 +160,8 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
{
u64 min_prot_len;
- int ret, status_old, status_new;
+ int ret;
+ u8 status_old[1] = {}, status_new[1] = {};
u8 mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
u8 pow, val;
@@ -172,7 +173,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
if (ret)
return ret;
- status_old = nor->bouncebuf[0];
+ status_old[0] = nor->bouncebuf[0];
/* If nothing in our range is unlocked, we don't need to do anything */
if (spi_nor_is_locked_sr(nor, ofs, len, status_old))
@@ -217,7 +218,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
return -EINVAL;
}
- status_new = (status_old & ~mask & ~tb_mask) | val;
+ status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val;
/*
* Disallow further writes if WP# pin is neither left floating nor
@@ -225,20 +226,20 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
* WP# pin hard strapped to GND can be a valid use case.
*/
if (!(nor->flags & SNOR_F_NO_WP))
- status_new |= SR_SRWD;
+ status_new[0] |= SR_SRWD;
if (!use_top)
- status_new |= tb_mask;
+ status_new[0] |= tb_mask;
/* Don't bother if they're the same */
- if (status_new == status_old)
+ if (status_new[0] == status_old[0])
return 0;
/* Only modify protection if it will not unlock other areas */
- if ((status_new & mask) < (status_old & mask))
+ if ((status_new[0] & mask) < (status_old[0] & mask))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new);
+ return spi_nor_write_sr_and_check(nor, status_new[0]);
}
/*
@@ -249,7 +250,8 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
{
u64 min_prot_len;
- int ret, status_old, status_new;
+ int ret;
+ u8 status_old[1], status_new[1];
u8 mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
u8 pow, val;
@@ -261,7 +263,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
if (ret)
return ret;
- status_old = nor->bouncebuf[0];
+ status_old[0] = nor->bouncebuf[0];
/* If nothing in our range is locked, we don't need to do anything */
if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old))
@@ -310,24 +312,24 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
return -EINVAL;
}
- status_new = (status_old & ~mask & ~tb_mask) | val;
+ status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val;
/* Don't protect status register if we're fully unlocked */
if (lock_len == 0)
- status_new &= ~SR_SRWD;
+ status_new[0] &= ~SR_SRWD;
if (!use_top)
- status_new |= tb_mask;
+ status_new[0] |= tb_mask;
/* Don't bother if they're the same */
- if (status_new == status_old)
+ if (status_new[0] == status_old[0])
return 0;
/* Only modify protection if it will not lock other areas */
- if ((status_new & mask) > (status_old & mask))
+ if ((status_new[0] & mask) > (status_old[0] & mask))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new);
+ return spi_nor_write_sr_and_check(nor, status_new[0]);
}
/*
@@ -345,7 +347,7 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
if (ret)
return ret;
- return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf[0]);
+ return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf);
}
static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 11/28] mtd: spi-nor: swp: Create a helper that writes SR, CR and checks
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
There are many helpers already to either read and/or write SR and/or CR,
as well as sometimes check the returned values. In order to be able to
switch from a 1 byte status register to a 2 bytes status register while
keeping the same level of verification, let's introduce a new helper
that writes them both (atomically) and then reads them back (separated)
to compare the values.
In case 2 bytes registers are not supported, we still have the usual
fallback available in the helper being exported to the rest of the core.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/core.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/mtd/spi-nor/core.h | 1 +
2 files changed, 66 insertions(+)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 394c27de02d6..2799c21d0b67 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -976,6 +976,54 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
return 0;
}
+/**
+ * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the
+ * Configuration Register in one shot. Ensure that the bytes written in both
+ * registers match the received value.
+ * @nor: pointer to a 'struct spi_nor'.
+ * @regs: two-byte array with values to be written to the status and
+ * configuration registers.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
+{
+ u8 written_regs[2];
+ int ret;
+
+ written_regs[0] = regs[0];
+ written_regs[1] = regs[1];
+ nor->bouncebuf[0] = regs[0];
+ nor->bouncebuf[1] = regs[1];
+
+ ret = spi_nor_write_sr(nor, nor->bouncebuf, 2);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
+ if (ret)
+ return ret;
+
+ if (written_regs[0] != nor->bouncebuf[0]) {
+ dev_dbg(nor->dev, "SR: Read back test failed\n");
+ return -EIO;
+ }
+
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ return 0;
+
+ ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]);
+ if (ret)
+ return ret;
+
+ if (written_regs[1] != nor->bouncebuf[1]) {
+ dev_dbg(nor->dev, "CR: read back test failed\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
/**
* spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that
* the byte written match the received value without affecting other bits in the
@@ -993,6 +1041,23 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
return spi_nor_write_sr1_and_check(nor, sr1);
}
+/**
+ * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that
+ * the byte written match the received value. Same for the Control Register if
+ * available.
+ * @nor: pointer to a 'struct spi_nor'.
+ * @regs: byte array to be written to the registers.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
+{
+ if (nor->flags & SNOR_F_HAS_16BIT_SR)
+ return spi_nor_write_16bit_sr_cr_and_check(nor, regs);
+
+ return spi_nor_write_sr1_and_check(nor, regs[0]);
+}
+
/**
* spi_nor_write_sr2() - Write the Status Register 2 using the
* SPINOR_OP_WRSR2 (3eh) command.
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 22f21497c720..7f92586b326b 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -637,6 +637,7 @@ int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr);
+int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs);
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
u8 *buf);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 12/28] mtd: spi-nor: swp: Rename a mask
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
"mask" is not very descriptive when we already manipulate two masks, and
soon will manipulate three. Rename it "bp_mask" to align with the
existing "tb_mask" and soon "cmp_mask".
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 61830f18a147..07269e09370a 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -59,9 +59,9 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_
u64 *len)
{
u64 min_prot_len;
- u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
- u8 bp, val = sr[0] & mask;
+ u8 bp, val = sr[0] & bp_mask;
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
val = (val & ~SR_BP3_BIT6) | SR_BP3;
@@ -162,7 +162,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
u64 min_prot_len;
int ret;
u8 status_old[1] = {}, status_new[1] = {};
- u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
u8 pow, val;
loff_t lock_len;
@@ -201,7 +201,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
lock_len = ofs + len;
if (lock_len == nor->params->size) {
- val = mask;
+ val = bp_mask;
} else {
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
@@ -210,15 +210,15 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
val = (val & ~SR_BP3) | SR_BP3_BIT6;
- if (val & ~mask)
+ if (val & ~bp_mask)
return -EINVAL;
/* Don't "lock" with no region! */
- if (!(val & mask))
+ if (!(val & bp_mask))
return -EINVAL;
}
- status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val;
+ status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val;
/*
* Disallow further writes if WP# pin is neither left floating nor
@@ -236,7 +236,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
return 0;
/* Only modify protection if it will not unlock other areas */
- if ((status_new[0] & mask) < (status_old[0] & mask))
+ if ((status_new[0] & bp_mask) < (status_old[0] & bp_mask))
return -EINVAL;
return spi_nor_write_sr_and_check(nor, status_new[0]);
@@ -252,7 +252,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
u64 min_prot_len;
int ret;
u8 status_old[1], status_new[1];
- u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
u8 pow, val;
loff_t lock_len;
@@ -308,11 +308,11 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
val = (val & ~SR_BP3) | SR_BP3_BIT6;
/* Some power-of-two sizes may not be supported */
- if (val & ~mask)
+ if (val & ~bp_mask)
return -EINVAL;
}
- status_new[0] = (status_old[0] & ~mask & ~tb_mask) | val;
+ status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val;
/* Don't protect status register if we're fully unlocked */
if (lock_len == 0)
@@ -326,7 +326,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
return 0;
/* Only modify protection if it will not lock other areas */
- if ((status_new[0] & mask) > (status_old[0] & mask))
+ if ((status_new[0] & bp_mask) > (status_old[0] & bp_mask))
return -EINVAL;
return spi_nor_write_sr_and_check(nor, status_new[0]);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 13/28] mtd: spi-nor: swp: Create a TB intermediate variable
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
Ease the future reuse of the tb (Top/Bottom) boolean by creating an
intermediate variable.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 07269e09370a..540cd221c455 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -62,6 +62,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
u8 bp, val = sr[0] & bp_mask;
+ bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0;
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
val = (val & ~SR_BP3_BIT6) | SR_BP3;
@@ -81,7 +82,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_
if (*len > nor->params->size)
*len = nor->params->size;
- if (nor->flags & SNOR_F_HAS_SR_TB && sr[0] & tb_mask)
+ if (tb)
*ofs = 0;
else
*ofs = nor->params->size - *len;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 14/28] mtd: spi-nor: swp: Create helpers for building the SR register
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
The status register contains 3 or 4 BP (Block Protect) bits, 0 or 1
TB (Top/Bottom) bit, soon 0 or 1 CMP (Complement) bit. The last BP bit
and the TB bit locations change between vendors. The whole logic of
buildling the content of the status register based on some input
conditions is used two times and soon will be used 4 times.
Create dedicated helpers for these steps.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 83 +++++++++++++++++++++++++++++------------------
1 file changed, 51 insertions(+), 32 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 540cd221c455..8aa0fe297188 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -125,6 +125,43 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
}
+static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow)
+{
+ u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 val = pow << SR_BP_SHIFT;
+
+ if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
+ val = (val & ~SR_BP3) | SR_BP3_BIT6;
+
+ if (val & ~mask)
+ return -EINVAL;
+
+ sr[0] = val;
+
+ return 0;
+}
+
+static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr,
+ u8 pow, bool use_top)
+{
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ int ret;
+
+ new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask;
+
+ /* Build BP field */
+ ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow);
+ if (ret)
+ return ret;
+
+ /* Build TB field */
+ if (!use_top)
+ new_sr[0] |= tb_mask;
+
+ return 0;
+}
+
/*
* Lock a region of the flash. Compatible with ST Micro and similar flash.
* Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status
@@ -164,11 +201,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
int ret;
u8 status_old[1] = {}, status_new[1] = {};
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
- u8 tb_mask = spi_nor_get_sr_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;
bool use_top;
+ u8 pow;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
@@ -202,24 +238,19 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
lock_len = ofs + len;
if (lock_len == nor->params->size) {
- val = bp_mask;
+ pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0);
} else {
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- val = pow << SR_BP_SHIFT;
-
- if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
- val = (val & ~SR_BP3) | SR_BP3_BIT6;
-
- if (val & ~bp_mask)
- return -EINVAL;
-
- /* Don't "lock" with no region! */
- if (!(val & bp_mask))
- return -EINVAL;
}
- status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val;
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
+ if (ret)
+ return ret;
+
+ /* Don't "lock" with no region! */
+ if (!(status_new[0] & bp_mask))
+ return -EINVAL;
/*
* Disallow further writes if WP# pin is neither left floating nor
@@ -229,9 +260,6 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
if (!(nor->flags & SNOR_F_NO_WP))
status_new[0] |= SR_SRWD;
- if (!use_top)
- status_new[0] |= tb_mask;
-
/* Don't bother if they're the same */
if (status_new[0] == status_old[0])
return 0;
@@ -254,11 +282,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
int ret;
u8 status_old[1], status_new[1];
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
- u8 tb_mask = spi_nor_get_sr_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;
bool use_top;
+ u8 pow;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
@@ -299,29 +326,21 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
lock_len = ofs;
if (lock_len == 0) {
- val = 0; /* fully unlocked */
+ pow = 0; /* fully unlocked */
} else {
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- val = pow << SR_BP_SHIFT;
- if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
- val = (val & ~SR_BP3) | SR_BP3_BIT6;
-
- /* Some power-of-two sizes may not be supported */
- if (val & ~bp_mask)
- return -EINVAL;
}
- status_new[0] = (status_old[0] & ~bp_mask & ~tb_mask) | val;
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
+ if (ret)
+ return ret;
/* Don't protect status register if we're fully unlocked */
if (lock_len == 0)
status_new[0] &= ~SR_SRWD;
- if (!use_top)
- status_new[0] |= tb_mask;
-
/* Don't bother if they're the same */
if (status_new[0] == status_old[0])
return 0;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 15/28] mtd: spi-nor: swp: Simplify checking the locked/unlocked range
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
In both the locking/unlocking steps, at the end we verify whether we do
not lock/unlock more than requested (in which case an error must be
returned).
While being possible to do that with very simple mask comparisons, it
does not scale when adding extra locking features such as the CMP
possibility. In order to make these checks slightly easier to read and
more future proof, use existing helpers to read the (future) status
register, extract the covered range, and compare it with very usual
algebric comparisons.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 8aa0fe297188..a45627380363 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -200,7 +200,8 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
u64 min_prot_len;
int ret;
u8 status_old[1] = {}, status_new[1] = {};
- u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ loff_t ofs_old, ofs_new;
+ u64 len_old, len_new;
loff_t lock_len;
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
bool use_top;
@@ -248,10 +249,6 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
if (ret)
return ret;
- /* Don't "lock" with no region! */
- if (!(status_new[0] & bp_mask))
- return -EINVAL;
-
/*
* Disallow further writes if WP# pin is neither left floating nor
* wrongly tied to GND (that includes internal pull-downs).
@@ -264,8 +261,16 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
if (status_new[0] == status_old[0])
return 0;
+ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+
+ /* Don't "lock" with no region! */
+ if (!len_new)
+ return -EINVAL;
+
/* Only modify protection if it will not unlock other areas */
- if ((status_new[0] & bp_mask) < (status_old[0] & bp_mask))
+ if (len_old &&
+ (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
return -EINVAL;
return spi_nor_write_sr_and_check(nor, status_new[0]);
@@ -281,7 +286,8 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
u64 min_prot_len;
int ret;
u8 status_old[1], status_new[1];
- u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ loff_t ofs_old, ofs_new;
+ u64 len_old, len_new;
loff_t lock_len;
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
bool use_top;
@@ -346,7 +352,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
return 0;
/* Only modify protection if it will not lock other areas */
- if ((status_new[0] & bp_mask) > (status_old[0] & bp_mask))
+ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ if (len_old && len_new &&
+ (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
return -EINVAL;
return spi_nor_write_sr_and_check(nor, status_new[0]);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 16/28] mtd: spi-nor: swp: Cosmetic changes
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
As a final preparation step for the introduction of CMP support, make
a few more cosmetic changes to simplify the reading of the diff when
adding the CMP feature. In particular, define "min_prot_len" earlier as
it will be reused and move the definition of the "ret" variable at the
end of the stack just because it looks better.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/swp.c | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index a45627380363..c22cb094b66a 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -197,14 +197,14 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr,
*/
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
{
- u64 min_prot_len;
- int ret;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
u8 status_old[1] = {}, status_new[1] = {};
loff_t ofs_old, ofs_new;
u64 len_old, len_new;
loff_t lock_len;
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
bool use_top;
+ int ret;
u8 pow;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
@@ -238,12 +238,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
else
lock_len = ofs + len;
- if (lock_len == nor->params->size) {
+ if (lock_len == nor->params->size)
pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0);
- } else {
- min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- }
ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
if (ret)
@@ -283,7 +281,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
*/
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
{
- u64 min_prot_len;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
int ret;
u8 status_old[1], status_new[1];
loff_t ofs_old, ofs_new;
@@ -331,14 +329,11 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
else
lock_len = ofs;
- if (lock_len == 0) {
+ if (lock_len == 0)
pow = 0; /* fully unlocked */
- } else {
- min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- }
-
ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top);
if (ret)
return ret;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 17/28] mtd: spi-nor: Create a local SR cache
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
In order to be able to generate debugfs output without having to
actually reach the flash, create a SPI NOR local cache of the status
registers. What matters in our case are all the bits related to sector
locking. As such, in order to make it clear that this cache is not
intended to be used anywhere else, we zero the irrelevant bits.
The cache is initialized once during the early init, and then maintained
every time the write protection scheme is updated.
Suggested-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/core.c | 6 +++++-
drivers/mtd/spi-nor/core.h | 1 +
drivers/mtd/spi-nor/swp.c | 35 +++++++++++++++++++++++++++++++++--
include/linux/mtd/spi-nor.h | 2 ++
4 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 2799c21d0b67..cdca0fd881a3 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3328,8 +3328,12 @@ static int spi_nor_init(struct spi_nor *nor)
*/
if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
(IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
- nor->flags & SNOR_F_SWP_IS_VOLATILE))
+ nor->flags & SNOR_F_SWP_IS_VOLATILE)) {
spi_nor_try_unlock_all(nor);
+ } else {
+ /* In the other cases, make sure the debugfs SR cache is up to date */
+ spi_nor_cache_sr_lock_bits(nor, NULL);
+ }
if (nor->addr_nbytes == 4 &&
nor->read_proto != SNOR_PROTO_8_8_8_DTR &&
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 7f92586b326b..16bf6190e55a 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -679,6 +679,7 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
void spi_nor_init_default_locking_ops(struct spi_nor *nor);
void spi_nor_try_unlock_all(struct spi_nor *nor);
+void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr);
void spi_nor_set_mtd_locking_ops(struct spi_nor *nor);
void spi_nor_set_mtd_otp_ops(struct spi_nor *nor);
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index c22cb094b66a..2455f1f6fdf1 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -162,6 +162,25 @@ static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr,
return 0;
}
+/*
+ * Keep a local cache containing all lock-related bits for debugfs use only.
+ * This way, debugfs never needs to access the flash directly.
+ */
+void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr)
+{
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+
+ if (!sr) {
+ if (spi_nor_read_sr(nor, nor->bouncebuf))
+ return;
+
+ sr = nor->bouncebuf;
+ }
+
+ nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD);
+}
+
/*
* Lock a region of the flash. Compatible with ST Micro and similar flash.
* Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status
@@ -271,7 +290,13 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
(ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new[0]);
+ ret = spi_nor_write_sr_and_check(nor, status_new[0]);
+ if (ret)
+ return ret;
+
+ spi_nor_cache_sr_lock_bits(nor, status_new);
+
+ return 0;
}
/*
@@ -353,7 +378,13 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
(ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new[0]);
+ ret = spi_nor_write_sr_and_check(nor, status_new[0]);
+ if (ret)
+ return ret;
+
+ spi_nor_cache_sr_lock_bits(nor, status_new);
+
+ return 0;
}
/*
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 90a0cf583512..9ad77f9e76c2 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -371,6 +371,7 @@ struct spi_nor_flash_parameter;
* @reg_proto: the SPI protocol for read_reg/write_reg/erase operations
* @sfdp: the SFDP data of the flash
* @debugfs_root: pointer to the debugfs directory
+ * @dfs_sr_cache: Status Register cached value for debugfs use only
* @controller_ops: SPI NOR controller driver specific operations.
* @params: [FLASH-SPECIFIC] SPI NOR flash parameters and settings.
* The structure includes legacy flash parameters and
@@ -409,6 +410,7 @@ struct spi_nor {
enum spi_nor_cmd_ext cmd_ext_type;
struct sfdp *sfdp;
struct dentry *debugfs_root;
+ u8 dfs_sr_cache[2];
const struct spi_nor_controller_ops *controller_ops;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 18/28] mtd: spi-nor: debugfs: Add locking support
From: Miquel Raynal @ 2026-05-07 16:46 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
The ioctl output may be counter intuitive in some cases. Asking for a
"locked status" over a region that is only partially locked will return
"unlocked" whereas in practice maybe the biggest part is actually
locked.
Knowing what is the real software locking state through debugfs would be
very convenient for development/debugging purposes, hence this proposal
for adding an extra block at the end of the file: a "locked sectors"
array which lists every section, if it is locked or not, showing both
the address ranges and the sizes in numbers of "lock sectors" (which on
small density devices is typically different than erase blocks).
Here is an example of output, what is after the "sector map" is new.
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name (null)
id ef a0 20 00 00 00
size 64.0 MiB
write size 1
page size 256
address nbytes 4
flags HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP
opcodes
read 0xec
dummy cycles 6
erase 0xdc
program 0x34
8D extension none
protocols
read 1S-4S-4S
write 1S-1S-4S
register 1S-1S-1S
erase commands
21 (4.00 KiB) [1]
dc (64.0 KiB) [3]
c7 (64.0 MiB)
sector map
region (in hex) | erase mask | overlaid
------------------+------------+---------
00000000-03ffffff | [ 3] | no
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-03ffffff | unlocked | 1024
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
Here are below more examples of output with various situations. The full
output of the "params" content has been manually removed to only show
what has been added and how it behaves.
$ flash_lock -l /dev/mtd0 0x3f00000 16
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-03efffff | unlocked | 1008
03f00000-03ffffff | locked | 16
$
$ flash_lock -u /dev/mtd0 0x3f00000 8
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-03f7ffff | unlocked | 1016
03f80000-03ffffff | locked | 8
$
$ flash_lock -u /dev/mtd0
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-03ffffff | unlocked | 1024
$
$ flash_lock -l /dev/mtd0
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-03ffffff | locked | 1024
$
$ flash_lock -u /dev/mtd0 0x20000 1022
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
region (in hex) | status | #sectors
------------------+----------+---------
00000000-0001ffff | locked | 2
00020000-03ffffff | unlocked | 1022
---
drivers/mtd/spi-nor/core.h | 8 ++++++++
drivers/mtd/spi-nor/debugfs.c | 27 +++++++++++++++++++++++++++
drivers/mtd/spi-nor/swp.c | 13 +++++++++----
3 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 16bf6190e55a..552e734c7107 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -287,6 +287,9 @@ struct spi_nor_erase_map {
* false otherwise. This feeback may be misleading because users
* may get an "unlocked" status even though a subpart of the region
* is effectively locked.
+ *
+ * If in doubt during development, check-out the debugfs output which tries to
+ * be more user friendly.
*/
struct spi_nor_locking_ops {
int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len);
@@ -678,6 +681,7 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
const struct sfdp_bfpt *bfpt);
void spi_nor_init_default_locking_ops(struct spi_nor *nor);
+bool spi_nor_has_default_locking_ops(struct spi_nor *nor);
void spi_nor_try_unlock_all(struct spi_nor *nor);
void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr);
void spi_nor_set_mtd_locking_ops(struct spi_nor *nor);
@@ -712,6 +716,10 @@ static inline bool spi_nor_needs_sfdp(const struct spi_nor *nor)
return !nor->info->size;
}
+u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor);
+void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len);
+bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr);
+
#ifdef CONFIG_DEBUG_FS
void spi_nor_debugfs_register(struct spi_nor *nor);
void spi_nor_debugfs_shutdown(void);
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index d0191eb9f879..36ec35d6b2dc 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/debugfs.h>
+#include <linux/math64.h>
#include <linux/mtd/spi-nor.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
@@ -77,10 +78,12 @@ static void spi_nor_print_flags(struct seq_file *s, unsigned long flags,
static int spi_nor_params_show(struct seq_file *s, void *data)
{
struct spi_nor *nor = s->private;
+ unsigned int min_prot_len = spi_nor_get_min_prot_length_sr(nor);
struct spi_nor_flash_parameter *params = nor->params;
struct spi_nor_erase_map *erase_map = ¶ms->erase_map;
struct spi_nor_erase_region *region = erase_map->regions;
const struct flash_info *info = nor->info;
+ loff_t lock_start, lock_length;
char buf[16], *str;
unsigned int i;
@@ -159,6 +162,30 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
region[i].overlaid ? "yes" : "no");
}
+ if (!spi_nor_has_default_locking_ops(nor))
+ return 0;
+
+ seq_puts(s, "\nlocked sectors\n");
+ seq_puts(s, " region (in hex) | status | #sectors\n");
+ seq_puts(s, " ------------------+----------+---------\n");
+
+ spi_nor_get_locked_range_sr(nor, nor->dfs_sr_cache, &lock_start, &lock_length);
+ if (!lock_length || lock_length == params->size) {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, params->size - 1,
+ lock_length ? " locked" : "unlocked",
+ div_u64(params->size, min_prot_len));
+ } else if (!lock_start) {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_length - 1,
+ " locked", div_u64(lock_length, min_prot_len));
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_length, params->size - 1,
+ "unlocked", div_u64(params->size - lock_length, min_prot_len));
+ } else {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_start - 1,
+ "unlocked", div_u64(lock_start, min_prot_len));
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_start, params->size - 1,
+ " locked", div_u64(lock_length, min_prot_len));
+ }
+
return 0;
}
DEFINE_SHOW_ATTRIBUTE(spi_nor_params);
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 2455f1f6fdf1..5d6c3afa36e3 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -34,7 +34,7 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
return 0;
}
-static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
+u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
{
unsigned int bp_slots, bp_slots_needed;
/*
@@ -55,8 +55,8 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
return sector_size;
}
-static void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs,
- u64 *len)
+void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs,
+ u64 *len)
{
u64 min_prot_len;
u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
@@ -114,7 +114,7 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
return (ofs >= lock_offs_max) || (offs_max <= lock_offs);
}
-static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr)
+bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr)
{
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
}
@@ -416,6 +416,11 @@ void spi_nor_init_default_locking_ops(struct spi_nor *nor)
nor->params->locking_ops = &spi_nor_sr_locking_ops;
}
+bool spi_nor_has_default_locking_ops(struct spi_nor *nor)
+{
+ return nor->params->locking_ops == &spi_nor_sr_locking_ops;
+}
+
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, u64 len)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 19/28] mtd: spi-nor: debugfs: Add a locked sectors map
From: Miquel Raynal @ 2026-05-07 16:47 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
In order to get a very clear view of the sectors being locked, besides
the `params` output giving the ranges, we may want to see a proper map
of the sectors and for each of them, their status. Depending on the use
case, this map may be easier to parse by humans and gives a more acurate
feeling of the situation. At least myself, for the few locking-related
developments I recently went through, I found it very useful to get a
clearer mental model of what was locked/unlocked.
Here is an example of output:
$ cat /sys/kernel/debug/spi-nor/spi0.0/locked-sectors-map
Locked sectors map (x: locked, .: unlocked, unit: 64kiB)
0x00000000 (# 0): ................ ................ ................ ................
0x00400000 (# 64): ................ ................ ................ ................
0x00800000 (# 128): ................ ................ ................ ................
0x00c00000 (# 192): ................ ................ ................ ................
0x01000000 (# 256): ................ ................ ................ ................
0x01400000 (# 320): ................ ................ ................ ................
0x01800000 (# 384): ................ ................ ................ ................
0x01c00000 (# 448): ................ ................ ................ ................
0x02000000 (# 512): ................ ................ ................ ................
0x02400000 (# 576): ................ ................ ................ ................
0x02800000 (# 640): ................ ................ ................ ................
0x02c00000 (# 704): ................ ................ ................ ................
0x03000000 (# 768): ................ ................ ................ ................
0x03400000 (# 832): ................ ................ ................ ................
0x03800000 (# 896): ................ ................ ................ ................
0x03c00000 (# 960): ................ ................ ................ ..............xx
The output is wrapped at 64 sectors, spaces every 16 sectors are
improving the readability, every line starts by the first sector
offset (hex) and number (decimal).
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/mtd/spi-nor/debugfs.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 36ec35d6b2dc..a07e879bfa25 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -190,6 +190,40 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
}
DEFINE_SHOW_ATTRIBUTE(spi_nor_params);
+static int spi_nor_locked_sectors_map_show(struct seq_file *s, void *data)
+{
+ struct spi_nor *nor = s->private;
+ struct spi_nor_flash_parameter *params = nor->params;
+ unsigned int min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ unsigned int offset = 0, sector = 0;
+ bool locked;
+ int i;
+
+ seq_printf(s, "Locked sectors map (x: locked, .: unlocked, unit: %dkiB)\n",
+ min_prot_len / 1024);
+ while (offset < params->size) {
+ seq_printf(s, " 0x%08x (#%5d): ", offset, sector);
+ for (i = 0; i < 64 && offset < params->size; i++) {
+ locked = spi_nor_is_locked_sr(nor, offset, min_prot_len,
+ nor->dfs_sr_cache);
+ if (locked)
+ seq_puts(s, "x");
+ else
+ seq_puts(s, ".");
+
+ if (((i + 1) % 16) == 0)
+ seq_puts(s, " ");
+
+ offset += min_prot_len;
+ sector++;
+ }
+ seq_puts(s, "\n");
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(spi_nor_locked_sectors_map);
+
static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap,
struct spi_nor_read_command *cmd)
{
@@ -275,6 +309,8 @@ void spi_nor_debugfs_register(struct spi_nor *nor)
debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops);
debugfs_create_file("capabilities", 0444, d, nor,
&spi_nor_capabilities_fops);
+ if (spi_nor_has_default_locking_ops(nor))
+ debugfs_create_file("locked-sectors-map", 0444, d, nor, &spi_nor_locked_sectors_map_fops);
}
void spi_nor_debugfs_shutdown(void)
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
* [PATCH v5 20/28] mtd: spi-nor: Add steps for testing locking support
From: Miquel Raynal @ 2026-05-07 16:47 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet,
Tudor Ambarus, Shuah Khan
Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260507-winbond-v6-18-rc1-spi-nor-swp-v5-0-93453e1a9597@bootlin.com>
As recently raised on the mailing list, it may be useful to propose a
list of steps to go through in order to proove the devices have been
described correctly, especially since all the block protection
information is not stored in any kind of table and is instead filled
manually by developpers.
Use the debugfs output to ease the comparison between expectations and
reality.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
Documentation/driver-api/mtd/spi-nor.rst | 132 +++++++++++++++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst
index 148fa4288760..b42de32c6eba 100644
--- a/Documentation/driver-api/mtd/spi-nor.rst
+++ b/Documentation/driver-api/mtd/spi-nor.rst
@@ -203,3 +203,135 @@ section, after the ``---`` marker.
mtd.writesize = 1
mtd.oobsize = 0
regions = 0
+
+5) If your flash supports locking, please go through the following test
+ procedure to make sure it correctly behaves. The below example
+ expects the typical situation where eraseblocks and lock sectors have
+ the same size. In case you enabled MTD_SPI_NOR_USE_4K_SECTORS, you
+ must adapt `bs` accordingly.
+
+ Warning: These tests may hard lock your device! Make sure:
+ - The device is not hard locked already (#WP strapped to low and
+ SR_SRWD bit set)
+ - If you have a WPn pin, you may want to set `no-wp` in your DT for
+ the time of the test, to only make use of software protection.
+ Otherwise, clearing the locking state depends on the WPn
+ signal and if it is tied to low, the flash will be permanently
+ locked.
+
+ Test full chip locking and make sure expectations, the MEMISLOCKED
+ ioctl output, the debugfs output and experimental results are all
+ aligned::
+
+ root@1:~# alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ root@1:~# flash_lock -u /dev/mtd0
+ root@1:~# flash_lock -i /dev/mtd0
+ Device: /dev/mtd0
+ Start: 0
+ Len: 0x4000000
+ Lock status: unlocked
+ Return code: 0
+ root@1:~# mtd_debug erase /dev/mtd0 0 2097152
+ Erased 2097152 bytes from address 0x00000000 in flash
+ root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test
+ Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read
+ Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ root@1:~# sha256sum spi*
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-03ffffff | unlocked | 1024
+
+ root@1:~# flash_lock -l /dev/mtd0
+ root@1:~# flash_lock -i /dev/mtd0
+ Device: /dev/mtd0
+ Start: 0
+ Len: 0x4000000
+ Lock status: locked
+ Return code: 1
+ root@1:~# mtd_debug erase /dev/mtd0 0 2097152
+ Erased 2097152 bytes from address 0x00000000 in flash
+ root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read
+ Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ root@1:~# sha256sum spi*
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test
+ root@1:~# dd if=/dev/urandom of=./spi_test2 bs=1M count=2
+ 2+0 records in
+ 2+0 records out
+ root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test2
+ Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read2
+ Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ root@1:~# sha256sum spi*
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read2
+ c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test
+ bea9334df51c620440f86751cba0799214a016329f1736f9456d40cf40efdc88 spi_test2
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-03ffffff | locked | 1024
+ root@1:~# flash_lock -u /dev/mtd0
+
+ Once we trust the debugfs output we can use it to test various
+ situations. Check top locking/unlocking (end of the device)::
+
+ root@1:~# size=$(cat /sys/class/mtd/mtd0/size)
+ root@1:~# bs=$(cat /sys/class/mtd/mtd0/erasesize)
+ root@1:~# nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*unlocked | //')
+ root@1:~# ss=$(($size / $nsectors))
+ root@1:~# bps=$(($ss / $bs))
+
+ root@1:~# flash_lock -u /dev/mtd0
+ root@1:~# flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-03fdffff | unlocked | 1022
+ 03fe0000-03ffffff | locked | 2
+ root@1:~# flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-03feffff | unlocked | 1023
+ 03ff0000-03ffffff | locked | 1
+
+ If the flash features 4 block protection bits (BP), we can protect
+ more than 4MB (typically 128 64kiB-blocks or more), with a finer
+ grain than locking the entire device::
+
+ root@1:~# flash_lock -u /dev/mtd0
+ root@1:~# flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps))
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-037fffff | unlocked | 896
+ 03800000-03ffffff | locked | 128
+
+ If the flash features a Top/Bottom (TB) bit, we can protect the
+ beginning of the flash::
+
+ root@1:~# flash_lock -u /dev/mtd0
+ root@1:~# flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-0001ffff | locked | 2
+ 00020000-03ffffff | unlocked | 1022
+ root@1:~# flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one
+ root@1:~# show_sectors
+ software locked sectors
+ region (in hex) | status | #sectors
+ ------------------+----------+---------
+ 00000000-0000ffff | locked | 1
+ 00010000-03ffffff | unlocked | 1023
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox