]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mtd: spi-nor: debugfs: Add locking support
authorMiquel Raynal <miquel.raynal@bootlin.com>
Tue, 26 May 2026 14:56:42 +0000 (16:56 +0200)
committerPratyush Yadav <pratyush@kernel.org>
Tue, 26 May 2026 15:21:05 +0000 (17:21 +0200)
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>
Signed-off-by: Pratyush Yadav <pratyush@kernel.org>
drivers/mtd/spi-nor/core.h
drivers/mtd/spi-nor/debugfs.c
drivers/mtd/spi-nor/swp.c

index cd355e94b97e520552160c0fb4416a679a3c2c0f..ce46771ecdc82f399ecdcb98d45d3ad37772346c 100644 (file)
@@ -287,6 +287,9 @@ struct spi_nor_erase_map {
  *             false otherwise. This feedback 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);
index d0191eb9f87956418dfd964fc1f16b21e3345049..82df8ad4176c974f7294a486b25f4127388b8dca 100644 (file)
@@ -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,11 +78,14 @@ 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;
+       u64 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 = &params->erase_map;
        struct spi_nor_erase_region *region = erase_map->regions;
        const struct flash_info *info = nor->info;
        char buf[16], *str;
+       loff_t lock_start;
+       u64 lock_length;
        unsigned int i;
 
        seq_printf(s, "name\t\t%s\n", info->name);
@@ -159,6 +163,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);
index cd37fec08c0efc0d106e9a8ef494fad91551872a..2411d8f1012d15cca21a34b5fc2216d1342cac13 100644 (file)
@@ -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);