Do not prevent writing to mtd partitions where a partition boundary sits
on a minor erasesize boundary.
This addresses a FIXME that has been present since the start of the
linux git history:
/* Doesn't start on a boundary of major erase size */
/* FIXME: Let it be writable if it is on a boundary of
* _minor_ erase size though */
Allow a uniform erase region spi-nor device to be configured
to use the non-uniform erase regions code path for an erase with:
CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
On supporting hardware (SECT_4K: majority of current SPI-NOR device)
provide the facility for an erase to use the least number
of SPI-NOR operations, as well as access to 4K erase without
requiring CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
Introduce erasesize_minor to the mtd struct,
the smallest erasesize supported by the device
On existing devices, this is useful where write support is wanted
for data on a 4K partition, such as some u-boot-env partitions,
or RouterBoot soft_config, while still netting the performance
benefits of using 64K sectors
Performance:
time mtd erase firmware
OpenWrt 5.10 ramips MT7621 w25q128jv 0xfc0000 partition length
Without this patch
MTD_SPI_NOR_USE_4K_SECTORS=y |n
real 2m 11.66s |0m 50.86s
user 0m 0.00s |0m 0.00s
sys 1m 56.20s |0m 50.80s
With this patch
MTD_SPI_NOR_USE_VARIABLE_ERASE=n|y |4K_SECTORS=y
real 0m 51.68s |0m 50.85s |2m 12.89s
user 0m 0.00s |0m 0.00s |0m 0.01s
sys 0m 46.94s |0m 50.38s |2m 12.46s
Signed-off-by: John Thomson <[email protected]>
---
Have not tested on variable erase regions device.
checkpatch does not like the printk(KERN_WARNING
these should be changed separately beforehand?
Changes RFC -> v1:
Fix uninitialized variable smatch warning
Reported-by: kernel test robot <[email protected]>
Reported-by: Dan Carpenter <[email protected]>
---
drivers/mtd/mtdpart.c | 52 ++++++++++++++++++++++++++++---------
drivers/mtd/spi-nor/Kconfig | 10 +++++++
drivers/mtd/spi-nor/core.c | 10 +++++--
include/linux/mtd/mtd.h | 2 ++
4 files changed, 60 insertions(+), 14 deletions(-)
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index 665fd9020b76..fe7626b5020e 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -38,10 +38,11 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
struct mtd_info *master = mtd_get_master(parent);
int wr_alignment = (parent->flags & MTD_NO_ERASE) ?
master->writesize : master->erasesize;
+ int wr_alignment_minor = 0;
u64 parent_size = mtd_is_partition(parent) ?
parent->part.size : parent->size;
struct mtd_info *child;
- u32 remainder;
+ u32 remainder, remainder_minor;
char *name;
u64 tmp;
@@ -143,6 +144,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
int i, max = parent->numeraseregions;
u64 end = child->part.offset + child->part.size;
struct mtd_erase_region_info *regions = parent->eraseregions;
+ uint32_t erasesize_minor = child->erasesize;
/* Find the first erase regions which is part of this
* partition. */
@@ -153,15 +155,24 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
if (i > 0)
i--;
- /* Pick biggest erasesize */
for (; i < max && regions[i].offset < end; i++) {
+ /* Pick biggest erasesize */
if (child->erasesize < regions[i].erasesize)
child->erasesize = regions[i].erasesize;
+ /* Pick smallest non-zero erasesize */
+ if ((erasesize_minor > regions[i].erasesize) && (regions[i].erasesize > 0))
+ erasesize_minor = regions[i].erasesize;
}
+
+ if (erasesize_minor < child->erasesize)
+ child->erasesize_minor = erasesize_minor;
+
BUG_ON(child->erasesize == 0);
} else {
/* Single erase size */
child->erasesize = master->erasesize;
+ if (master->erasesize_minor)
+ child->erasesize_minor = master->erasesize_minor;
}
/*
@@ -169,26 +180,43 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
* exposes several regions with different erasesize. Adjust
* wr_alignment accordingly.
*/
- if (!(child->flags & MTD_NO_ERASE))
+ if (!(child->flags & MTD_NO_ERASE)) {
wr_alignment = child->erasesize;
+ if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE) && child->erasesize_minor)
+ wr_alignment_minor = child->erasesize_minor;
+ }
tmp = mtd_get_master_ofs(child, 0);
remainder = do_div(tmp, wr_alignment);
if ((child->flags & MTD_WRITEABLE) && remainder) {
- /* Doesn't start on a boundary of major erase size */
- /* FIXME: Let it be writable if it is on a boundary of
- * _minor_ erase size though */
- child->flags &= ~MTD_WRITEABLE;
- printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
- part->name);
+ if (wr_alignment_minor) {
+ tmp = mtd_get_master_ofs(child, 0);
+ remainder_minor = do_div(tmp, wr_alignment_minor);
+ if (remainder_minor == 0)
+ child->erasesize = child->erasesize_minor;
+ }
+
+ if ((!wr_alignment_minor) || (wr_alignment_minor && remainder_minor != 0)) {
+ child->flags &= ~MTD_WRITEABLE;
+ printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
+ part->name);
+ }
}
tmp = mtd_get_master_ofs(child, 0) + child->part.size;
remainder = do_div(tmp, wr_alignment);
if ((child->flags & MTD_WRITEABLE) && remainder) {
- child->flags &= ~MTD_WRITEABLE;
- printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
- part->name);
+ if (wr_alignment_minor) {
+ tmp = mtd_get_master_ofs(child, 0) + child->part.size;
+ remainder_minor = do_div(tmp, wr_alignment_minor);
+ if (remainder_minor == 0)
+ child->erasesize = child->erasesize_minor;
+ }
+ if ((!wr_alignment_minor) || (wr_alignment_minor && remainder_minor != 0)) {
+ child->flags &= ~MTD_WRITEABLE;
+ printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
+ part->name);
+ }
}
child->size = child->part.size;
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 24cd25de2b8b..09df9f1a8127 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -10,6 +10,16 @@ menuconfig MTD_SPI_NOR
if MTD_SPI_NOR
+config MTD_SPI_NOR_USE_VARIABLE_ERASE
+ bool "Disable uniform_erase to allow use of all hardware supported erasesizes"
+ depends on !MTD_SPI_NOR_USE_4K_SECTORS
+ default n
+ help
+ Allow mixed use of all hardware supported erasesizes,
+ by forcing spi_nor to use the multiple eraseregions code path.
+ For example: A 68K erase will use one 64K erase, and one 4K erase
+ on supporting hardware.
+
config MTD_SPI_NOR_USE_4K_SECTORS
bool "Use small 4096 B erase sectors"
default y
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index bd2c7717eb10..43d9b54e7edd 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1262,6 +1262,8 @@ static u8 spi_nor_convert_3to4_erase(u8 opcode)
static bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
{
+ if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE))
+ return false;
return !!nor->params->erase_map.uniform_erase_type;
}
@@ -2381,6 +2383,7 @@ static int spi_nor_select_erase(struct spi_nor *nor)
{
struct spi_nor_erase_map *map = &nor->params->erase_map;
const struct spi_nor_erase_type *erase = NULL;
+ const struct spi_nor_erase_type *erase_minor = NULL;
struct mtd_info *mtd = &nor->mtd;
u32 wanted_size = nor->info->sector_size;
int i;
@@ -2413,8 +2416,9 @@ static int spi_nor_select_erase(struct spi_nor *nor)
*/
for (i = SNOR_ERASE_TYPE_MAX - 1; i >= 0; i--) {
if (map->erase_type[i].size) {
- erase = &map->erase_type[i];
- break;
+ if (!erase)
+ erase = &map->erase_type[i];
+ erase_minor = &map->erase_type[i];
}
}
@@ -2422,6 +2426,8 @@ static int spi_nor_select_erase(struct spi_nor *nor)
return -EINVAL;
mtd->erasesize = erase->size;
+ if (erase_minor && erase_minor->size < erase->size)
+ mtd->erasesize_minor = erase_minor->size;
return 0;
}
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index a89955f3cbc8..33eafa27da50 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -243,6 +243,8 @@ struct mtd_info {
* information below if they desire
*/
uint32_t erasesize;
+ /* "Minor" (smallest) erase size supported by the whole device */
+ uint32_t erasesize_minor;
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
--
2.31.1
On 08/06/21 02:07PM, John Thomson wrote:
> Do not prevent writing to mtd partitions where a partition boundary sits
> on a minor erasesize boundary.
> This addresses a FIXME that has been present since the start of the
> linux git history:
> /* Doesn't start on a boundary of major erase size */
> /* FIXME: Let it be writable if it is on a boundary of
> * _minor_ erase size though */
>
> Allow a uniform erase region spi-nor device to be configured
> to use the non-uniform erase regions code path for an erase with:
> CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
>
> On supporting hardware (SECT_4K: majority of current SPI-NOR device)
> provide the facility for an erase to use the least number
> of SPI-NOR operations, as well as access to 4K erase without
> requiring CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
>
> Introduce erasesize_minor to the mtd struct,
> the smallest erasesize supported by the device
Instead of having just a major and minor erase size, wouldn't it make
more sense to have a list of all supported erases on a sector? That is,
instead of hard coding two erase size instead of one, how about
generalizing the erase machinery to allow any number of erase sizes on a
sector and then choosing the most efficient one on run time?
For example, imagine a device can support 4K, 64K, and 256K erases on
each sector. Then you should be able to use 2 x 4K erases if you want to
erase 8K, 2 x 64K + 1 x 4K erases for 132K and so on.
I am not very familiar with MTD erase machinery so I would like someone
else to chime in, but I think my suggestion above is the better way
forward.
--
Regards,
Pratyush Yadav
Texas Instruments Inc.
On Thu, 10 Jun 2021, at 10:08, Pratyush Yadav wrote:
> On 08/06/21 02:07PM, John Thomson wrote:
> > Do not prevent writing to mtd partitions where a partition boundary sits
> > on a minor erasesize boundary.
> > This addresses a FIXME that has been present since the start of the
> > linux git history:
> > /* Doesn't start on a boundary of major erase size */
> > /* FIXME: Let it be writable if it is on a boundary of
> > * _minor_ erase size though */
> >
> > Allow a uniform erase region spi-nor device to be configured
> > to use the non-uniform erase regions code path for an erase with:
> > CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
> >
> > On supporting hardware (SECT_4K: majority of current SPI-NOR device)
> > provide the facility for an erase to use the least number
> > of SPI-NOR operations, as well as access to 4K erase without
> > requiring CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
> >
> > Introduce erasesize_minor to the mtd struct,
> > the smallest erasesize supported by the device
>
> Instead of having just a major and minor erase size, wouldn't it make
> more sense to have a list of all supported erases on a sector? That is,
> instead of hard coding two erase size instead of one, how about
> generalizing the erase machinery to allow any number of erase sizes on a
> sector and then choosing the most efficient one on run time?
>
> For example, imagine a device can support 4K, 64K, and 256K erases on
> each sector. Then you should be able to use 2 x 4K erases if you want to
> erase 8K, 2 x 64K + 1 x 4K erases for 132K and so on.
Thank for you the feedback Pratyush,
Yes, this is what I am attempting to do.
mtdpart only sets if the partition can be writeable, it does not do the erasing.
Due to this, I thought that only the smallest erasesize should matter to mtdpart.
The erase is carried out by the mtd device, so for an SPI-NOR device,
spi_nor_erase_multi_sectors can be used to select and action the best combination of erases.
This is done by this patch when CONFIG_MTD_SPI_NOR_USE_VARIABLE_ERASE=y
Cheers,
--
John Thomson