From nobody Sat Jul 25 02:13:44 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B55B943745B for ; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; cv=none; b=ZUKQwW3n0gZnBJ9G6pbvSzvX4d2ipCl2TI/+A+YiUNOrCGWhG5DfWyitdTU3wmmmw6Xbdz2ZRr9HPSUvi6YdkTL3Sp67O8LhBdGnwNPFRGFjJ5fGykCHWOskcVSU0qpkEDcUWN9nXiMX7mUKJL2HW94/JfLP6OMwb/SAU0haGA8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; c=relaxed/simple; bh=kZOelfT1oQ41rQ99CFWXu/9dIJh5KGjwHWSuOwfrbNc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=b2q3VI9nkQovw7LGfFGu2OYrCBnPhrWMow6gN2Je1SXpmecnHVPuaqczuKMwLhWJkWQSjT55s0DxyaUdfCCCMyTa2g4mRwxVVNa8kiUjNuulUnOrWQm1R9ztMQrJ1NtqfNFsUvHT3PyQ3UtVdDAGBckQv3nB2wURiFrag+7ZEKQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=dHjhPnN/; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="dHjhPnN/" Received: by smtp.kernel.org (Postfix) with ESMTPS id 49013C2BCF7; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784560424; bh=kZOelfT1oQ41rQ99CFWXu/9dIJh5KGjwHWSuOwfrbNc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=dHjhPnN/lJVIz0RnjpHCrrrNr+XxlkY7L8NxSaRSZznzV54tKib0oKu5fkXB7yYcm GAH1+WPF0fvW/mYLieROXWdx+CN3oPKNsJYCUPloppFQTHZNfIVNwJL0H6EUtGjST4 zlyDQUVbtt0wam++sUmDXw19iWHTzpVJTe2l08wroXrpYvRyko0dm/cxPhn4tjQwq5 r7gxKHN6ZO6zqwlD+D+Uw6Wqkay611VOWBboxbfzC/a4WV/LCqQJ7yrbzwOd3tHx8r bwOIxH7ZT6TBQSEl8h4WM9Md7VjmsEfbpyw+X1KYphpr2IJobE6tSsEGkSQLXBeZRb JLjOofdYCwq8Q== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2431DC4452B; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) From: Mateusz Litwin via B4 Relay Date: Mon, 20 Jul 2026 17:12:38 +0200 Subject: [PATCH RFC 1/3] mtd: spi-nor: allow multiple erase sizes on uniform flash Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-spi_nor_multisize_erase-v1-1-38d719bfe77b@nokia.com> References: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> In-Reply-To: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> To: Pratyush Yadav , Michael Walle , Takahiro Kuwano , Miquel Raynal , Richard Weinberger , Vignesh Raghavendra Cc: linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org, Mateusz Litwin X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784560422; l=8038; i=mateusz.litwin@nokia.com; s=20251217; h=from:subject:message-id; bh=0dMbUh0MFcxU9fENYEnsMbteWVGEkbGRMmTcYB3mrzE=; b=AAeWi5EgBol80Toz675Takkpkffc16C3UKCYCaK8A8idh15AKWtYElqIFjc42LOvZ4Pp9GpN4 +Jg4J3ohRhjAinzyDY42cTMsr+hqzsHIhxyeuvuxLu95IbcfALM7XXt X-Developer-Key: i=mateusz.litwin@nokia.com; a=ed25519; pk=9NV76cwWrtwYUektOrK/ht9GTzmhtkqSvghr3Td4hM4= X-Endpoint-Received: by B4 Relay for mateusz.litwin@nokia.com/20251217 with auth_id=579 X-Original-From: Mateusz Litwin Reply-To: mateusz.litwin@nokia.com From: Mateusz Litwin Introduce MTD_SPI_NOR_MULTI_ERASE_SIZE, which allows an SPI NOR MTD device to use multiple erase sizes on uniform flashes. When enabled, the driver keeps all supported erase types in the uniform region mask instead of collapsing to a single size, and routes multi-size uniform erases through spi_nor_erase_multi_sectors(). This is useful where write access is needed on a minor-aligned partition (for example u-boot-env, RouterBoot soft_config, or a boot header partition) while still benefiting from larger erase commands on bulk regions, instead of forcing CONFIG_MTD_SPI_NOR_USE_4K_SECTORS for the entire device. Also convert all erase-type opcodes to their 4-byte-address variants on uniform flashes, so multi-size erases work correctly on devices above 16 MiB. When patch 2 is applied on top, the multi_sectors routing introduced here is replaced by a dedicated spi_nor_erase_uniform() path. Signed-off-by: Mateusz Litwin --- drivers/mtd/spi-nor/Kconfig | 25 +++++++++++++++++++ drivers/mtd/spi-nor/core.c | 60 ++++++++++++++++++++++++++++++++---------= ---- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index fd05a24d64a9..b2f97117a057 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -19,10 +19,35 @@ config MTD_SPI_NOR_USE_4K_SECTORS Changing a small part of the flash's contents is usually faster with small sectors. On the other hand erasing should be faster when using 64 KiB block instead of 16 =C3=97 4 KiB sectors. + If MTD_SPI_NOR_MULTI_ERASE_SIZE is also enabled, the flash erasesize + is reported as 4096 B, but large erase requests may still use larger + erase sizes internally to improve performance. =20 Please note that some tools/drivers/filesystems may not work with 4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum). =20 +config MTD_SPI_NOR_MULTI_ERASE_SIZE + bool "Use multiple erase sizes on uniform flashes" + default n + help + Uniform SPI NOR flashes (whose erase map spans the whole device) may + still advertise several erase sizes, e.g. 4 KiB, 32 KiB and 64 KiB. + By default the driver collapses such a flash to a single erase size + and uses that one size for every erase request. + + Say Y here to let those flashes use multiple erase sizes for a single + erase request, exactly like non-uniform flashes already do: larger + blocks are used for the aligned bulk of the range, while smaller + blocks are used for the unaligned head and tail. This improves erase + performance when a smaller erasesize is exposed to userspace. + + This option interacts with MTD_SPI_NOR_USE_4K_SECTORS: when both are + enabled, the 4096 B erase size is exposed through mtd->erasesize, but + the driver still uses multiple erase sizes internally to improve + performance. + + If unsure, say N. + choice prompt "Software write protection at boot" default MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index b25d1a870a22..80c5a7940fae 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,6 +7,7 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ =20 +#include #include #include #include @@ -1225,22 +1226,34 @@ static bool spi_nor_has_uniform_erase(const struct = spi_nor *nor) return !!nor->params->erase_map.uniform_region.erase_mask; } =20 +static bool spi_nor_has_multiple_uniform_erase_sizes(const struct spi_nor = *nor) +{ + return hweight8(nor->params->erase_map.uniform_region.erase_mask) > 1; +} + static void spi_nor_set_4byte_opcodes(struct spi_nor *nor) { + struct spi_nor_erase_map *map =3D &nor->params->erase_map; + struct spi_nor_erase_type *erase; + int i; + nor->read_opcode =3D spi_nor_convert_3to4_read(nor->read_opcode); nor->program_opcode =3D spi_nor_convert_3to4_program(nor->program_opcode); nor->erase_opcode =3D spi_nor_convert_3to4_erase(nor->erase_opcode); =20 - if (!spi_nor_has_uniform_erase(nor)) { - struct spi_nor_erase_map *map =3D &nor->params->erase_map; - struct spi_nor_erase_type *erase; - int i; - - for (i =3D 0; i < SNOR_ERASE_TYPE_MAX; i++) { - erase =3D &map->erase_type[i]; - erase->opcode =3D - spi_nor_convert_3to4_erase(erase->opcode); - } + /* + * Convert the opcode of every erase type, including for uniform + * flashes. For a uniform flash driven with a single erase type + * the converted erase_type opcode matches the nor->erase_opcode + * converted just above (spi_nor_select_erase() copied it from the same + * erase type), so >16 MiB uniform flashes keep erasing correctly. + * The erase map is always initialized, so we can safely convert all + * erase types, to keep the erase map in a consistent state to flash + * mode. + */ + for (i =3D 0; i < SNOR_ERASE_TYPE_MAX; i++) { + erase =3D &map->erase_type[i]; + erase->opcode =3D spi_nor_convert_3to4_erase(erase->opcode); } } =20 @@ -1710,7 +1723,7 @@ static int spi_nor_init_erase_cmd_list(struct spi_nor= *nor, } =20 /** - * spi_nor_erase_multi_sectors() - perform a non-uniform erase + * spi_nor_erase_multi_sectors() - perform a multi-size erase * @nor: pointer to a 'struct spi_nor' * @addr: offset in the serial flash memory * @len: number of bytes to erase @@ -1830,7 +1843,8 @@ static int spi_nor_erase(struct mtd_info *mtd, struct= erase_info *instr) dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, (long long)instr->len); =20 - if (spi_nor_has_uniform_erase(nor)) { + if (spi_nor_has_uniform_erase(nor) && + !spi_nor_has_multiple_uniform_erase_sizes(nor)) { div_u64_rem(instr->len, mtd->erasesize, &rem); if (rem) return -EINVAL; @@ -1864,7 +1878,8 @@ static int spi_nor_erase(struct mtd_info *mtd, struct= erase_info *instr) */ =20 /* "sector"-at-a-time erase */ - } else if (spi_nor_has_uniform_erase(nor)) { + } else if (spi_nor_has_uniform_erase(nor) && + !spi_nor_has_multiple_uniform_erase_sizes(nor)) { while (len) { ret =3D spi_nor_lock_device(nor); if (ret) @@ -2652,8 +2667,9 @@ static int spi_nor_select_pp(struct spi_nor *nor, * spi_nor_select_uniform_erase() - select optimum uniform erase type * @map: the erase map of the SPI NOR * - * Once the optimum uniform sector erase command is found, disable all the - * other. + * Select the optimum uniform sector erase type. When + * CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is disabled, disable all other erase + * types in the uniform region erase mask. * * Return: pointer to erase type on success, NULL otherwise. */ @@ -2700,8 +2716,12 @@ spi_nor_select_uniform_erase(struct spi_nor_erase_ma= p *map) if (!erase) return NULL; =20 - /* Disable all other Sector Erase commands. */ - map->uniform_region.erase_mask =3D BIT(erase - map->erase_type); + /* + * Disable all other Sector Erase commands only when + * CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is disabled. + */ + if (!IS_ENABLED(CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE)) + map->uniform_region.erase_mask =3D BIT(erase - map->erase_type); return erase; } =20 @@ -2719,6 +2739,12 @@ static int spi_nor_select_erase(struct spi_nor *nor) * So to be backward compatible, the new implementation also tries to * manage the SPI flash memory as uniform with a single erase sector * size, when possible. + * Exception is when CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is enabled. + * We use multiple erase sizes for the SPI flash memory, but we still + * need to select one of the supported erase sizes to set + * mtd->erasesize. The selected erase size will be exposed as the + * uniform erase size but all other erase sizes are still available + * for use. */ if (spi_nor_has_uniform_erase(nor)) { erase =3D spi_nor_select_uniform_erase(map); --=20 2.43.0 From nobody Sat Jul 25 02:13:44 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B509C43744B for ; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; cv=none; b=QlW2Lc+gSgNCzkTvlUmwIlSxyRypeD6y373/Omeh5ZfOj3mEm0KBuYhEQz5fErSGq62eASiZ5iOLO17vTw6msp0TH+iMaUjfAtA0PcOLe6sogPhMUq0c2fHt7Z94VO4EwZkoL2CrLAxSRSCQ2GFMCgDHSLCqEQmlSOxm4lJryeM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; c=relaxed/simple; bh=6yLNeKMHGdQxrGTjRv/adjQdu5KfwGWP8L/InLeKYv0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=fmJQVEhcVDh7ZCpLbuF/L1cXT+fDIhYb4RWhbLn9Xy28s93Oz/rgMpK31UdiTr0P3O7p0cb6u8JoopiJdDa4x/dzOcj4L6jI8tBFgY2xxZgrzr5x/CpXMrvaEXtDvlwackCWE8ooTT/JDajzotWnu/cK2AvV0FAj6TAO/5dtW+M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=m1jVbko9; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="m1jVbko9" Received: by smtp.kernel.org (Postfix) with ESMTPS id 51220C2BCC9; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784560424; bh=6yLNeKMHGdQxrGTjRv/adjQdu5KfwGWP8L/InLeKYv0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=m1jVbko9OiUvqJjmTwGCa475uAk+/R0of5/waTJ4H4UZp0MIxqGJe2bXE10SEr6Vd qEqSbbinHAGLIOfgib5FKSU2yxAfaPCG8g/9kS3/ehOfIF+dEd0OsxuzQbTBSfUrsZ +p7P/Ny3Gxat0/9HJB5wV7IWqLsY2Cn3czodZ/j/N19xzClPLHSJLTfUVgY62paNpP 1WtLPyxvRtdOaszC7t1jTR2U+B4BAZMI3hqQ5cwVYRdHNcplztZI4/DEXB13Uhq8u/ zYlLPtpA26cmyqEhRJf3Wa0hnGLGd1UdWjpAcKCdwmapm7Jpd4g7chM+bPNDYhYCUT OOd9X8sd4bgEQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 33A0CC4452D; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) From: Mateusz Litwin via B4 Relay Date: Mon, 20 Jul 2026 17:12:39 +0200 Subject: [PATCH RFC 2/3] mtd: spi-nor: add dedicated multi-size uniform erase path Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-spi_nor_multisize_erase-v1-2-38d719bfe77b@nokia.com> References: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> In-Reply-To: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> To: Pratyush Yadav , Michael Walle , Takahiro Kuwano , Miquel Raynal , Richard Weinberger , Vignesh Raghavendra Cc: linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org, Mateusz Litwin X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784560422; l=10478; i=mateusz.litwin@nokia.com; s=20251217; h=from:subject:message-id; bh=3dVnZER5+S/Zzb3IHLlukQyLRWdKKcKIEqJEBQYJD5Y=; b=77IgsA9QMuajC3iBRPFvm+vxGOeGnjJCI7RzQ45v8l0ZJnZcBcLZzbzm55grYfQXK3Uyyh6gp zoi/a4EoTKcCBDwD/xHHXbpNICdSUQtwiQBaO+J+pvf9ow5CVbxfYpH X-Developer-Key: i=mateusz.litwin@nokia.com; a=ed25519; pk=9NV76cwWrtwYUektOrK/ht9GTzmhtkqSvghr3Td4hM4= X-Endpoint-Received: by B4 Relay for mateusz.litwin@nokia.com/20251217 with auth_id=579 X-Original-From: Mateusz Litwin Reply-To: mateusz.litwin@nokia.com From: Mateusz Litwin Implement a dedicated uniform erase path for flashes with multiple erase sizes, instead of reusing the non-uniform erase path introduced in patch 1. The non-uniform path allocates an erase command list and walks the region tree on every request. For uniform flashes that work is unnecessary. The dedicated flow iterates the range directly and selects the erase type per step, which lowers CPU and memory overhead. Refactor the single-sector erase sequence into spi_nor_erase_one(), shared with spi_nor_erase_multi_sectors(). Validate multi-size uniform requests through spi_nor_is_uniform_erasable() before locking the device: address and length must be aligned to the smallest supported uniform erase size. This allows erase requests smaller than mtd->erasesize (e.g. a 32 KiB boot header region) while still using larger erase commands for aligned bulk portions. When CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is disabled, the legacy single-size uniform behavior is unchanged: only the length is checked against mtd->erasesize and address alignment is not validated. Signed-off-by: Mateusz Litwin --- drivers/mtd/spi-nor/core.c | 208 ++++++++++++++++++++++++++++++-----------= ---- 1 file changed, 141 insertions(+), 67 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 80c5a7940fae..4e13919d238a 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -7,7 +7,6 @@ * Copyright (C) 2014, Freescale Semiconductor, Inc. */ =20 -#include #include #include #include @@ -1226,11 +1225,6 @@ static bool spi_nor_has_uniform_erase(const struct s= pi_nor *nor) return !!nor->params->erase_map.uniform_region.erase_mask; } =20 -static bool spi_nor_has_multiple_uniform_erase_sizes(const struct spi_nor = *nor) -{ - return hweight8(nor->params->erase_map.uniform_region.erase_mask) > 1; -} - static void spi_nor_set_4byte_opcodes(struct spi_nor *nor) { struct spi_nor_erase_map *map =3D &nor->params->erase_map; @@ -1243,13 +1237,9 @@ static void spi_nor_set_4byte_opcodes(struct spi_nor= *nor) =20 /* * Convert the opcode of every erase type, including for uniform - * flashes. For a uniform flash driven with a single erase type - * the converted erase_type opcode matches the nor->erase_opcode - * converted just above (spi_nor_select_erase() copied it from the same - * erase type), so >16 MiB uniform flashes keep erasing correctly. - * The erase map is always initialized, so we can safely convert all - * erase types, to keep the erase map in a consistent state to flash - * mode. + * flashes. The erase map is always initialized, so we can safely + * convert all erase types, even if they are not used, to keep the + * erase map in a consistent state with address mode. */ for (i =3D 0; i < SNOR_ERASE_TYPE_MAX; i++) { erase =3D &map->erase_type[i]; @@ -1550,6 +1540,35 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 ad= dr) nor->bouncebuf, nor->addr_nbytes); } =20 +/** + * spi_nor_erase_one() - erase a single sector at the given address + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the serial flash memory + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_erase_one(struct spi_nor *nor, u32 addr) +{ + int ret; + + ret =3D spi_nor_lock_device(nor); + if (ret) + return ret; + + ret =3D spi_nor_write_enable(nor); + if (ret) { + spi_nor_unlock_device(nor); + return ret; + } + + ret =3D spi_nor_erase_sector(nor, addr); + spi_nor_unlock_device(nor); + if (ret) + return ret; + + return spi_nor_wait_till_ready(nor); +} + /** * spi_nor_div_by_erase_size() - calculate remainder and update new divide= nd * @erase: pointer to a structure that describes a SPI NOR erase type @@ -1566,6 +1585,47 @@ static u64 spi_nor_div_by_erase_size(const struct sp= i_nor_erase_type *erase, return dividend >> erase->size_shift; } =20 +/** + * spi_nor_is_uniform_erasable() - check if a uniform erase request is val= id + * @nor: pointer to a 'struct spi_nor' + * @instr: pointer to 'struct erase_info' + * + * When multiple erase sizes are enabled on a uniform flash, verify that t= he + * requested address and length are aligned to the smallest supported erase + * size in the uniform region. + * + * Return: true if the range can be erased, false otherwise. + */ +static bool spi_nor_is_uniform_erasable(const struct spi_nor *nor, + struct erase_info *instr) +{ + const struct spi_nor_erase_map *map =3D &nor->params->erase_map; + u8 erase_mask =3D map->uniform_region.erase_mask; + const struct spi_nor_erase_type *erase =3D NULL; + int i; + u32 rem; + + for (i =3D 0; i < SNOR_ERASE_TYPE_MAX; i++) { + if (erase_mask & BIT(i) && map->erase_type[i].size) { + erase =3D &map->erase_type[i]; + break; + } + } + + if (unlikely(!erase)) + return false; + + spi_nor_div_by_erase_size(erase, instr->addr, &rem); + if (rem) + return false; + + div_u64_rem(instr->len, erase->size, &rem); + if (rem) + return false; + + return true; +} + /** * spi_nor_find_best_erase_type() - find the best erase type for the given * offset in the serial flash memory and the @@ -1617,6 +1677,49 @@ spi_nor_find_best_erase_type(const struct spi_nor_er= ase_map *map, return NULL; } =20 +/** + * spi_nor_erase_uniform() - erase a range on a uniform flash + * @nor: pointer to a 'struct spi_nor' + * @addr: offset in the serial flash memory + * @len: number of bytes to erase + * + * Return: 0 on success, -errno otherwise. + */ +static int spi_nor_erase_uniform(struct spi_nor *nor, u64 addr, u32 len) +{ + const struct spi_nor_erase_map *map =3D &nor->params->erase_map; + const struct spi_nor_erase_region *region =3D &map->uniform_region; + const struct spi_nor_erase_type *erase; + int ret; + /* + * When CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is disabled, erase_size is + * mtd->erasesize and nor->erase_opcode was set once at init by + * spi_nor_select_erase(). + */ + u32 erase_size =3D nor->mtd.erasesize; + + while (len) { + if (IS_ENABLED(CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE)) { + erase =3D spi_nor_find_best_erase_type(map, region, addr, + len); + if (unlikely(!erase)) + return -EINVAL; + + nor->erase_opcode =3D erase->opcode; + erase_size =3D erase->size; + } + + ret =3D spi_nor_erase_one(nor, addr); + if (ret) + return ret; + + addr +=3D erase_size; + len -=3D erase_size; + } + + return 0; +} + /** * spi_nor_init_erase_cmd() - initialize an erase command * @region: pointer to a structure that describes a SPI NOR erase region @@ -1749,22 +1852,7 @@ static int spi_nor_erase_multi_sectors(struct spi_no= r *nor, u64 addr, u32 len) dev_vdbg(nor->dev, "erase_cmd->size =3D 0x%08x, erase_cmd->opcode =3D 0= x%02x, erase_cmd->count =3D %u\n", cmd->size, cmd->opcode, cmd->count); =20 - ret =3D spi_nor_lock_device(nor); - if (ret) - goto destroy_erase_cmd_list; - - ret =3D spi_nor_write_enable(nor); - if (ret) { - spi_nor_unlock_device(nor); - goto destroy_erase_cmd_list; - } - - ret =3D spi_nor_erase_sector(nor, addr); - spi_nor_unlock_device(nor); - if (ret) - goto destroy_erase_cmd_list; - - ret =3D spi_nor_wait_till_ready(nor); + ret =3D spi_nor_erase_one(nor, addr); if (ret) goto destroy_erase_cmd_list; =20 @@ -1843,11 +1931,20 @@ static int spi_nor_erase(struct mtd_info *mtd, stru= ct erase_info *instr) dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, (long long)instr->len); =20 - if (spi_nor_has_uniform_erase(nor) && - !spi_nor_has_multiple_uniform_erase_sizes(nor)) { - div_u64_rem(instr->len, mtd->erasesize, &rem); - if (rem) - return -EINVAL; + if (spi_nor_has_uniform_erase(nor)) { + if (IS_ENABLED(CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE)) { + if (!spi_nor_is_uniform_erasable(nor, instr)) + return -EINVAL; + } else { + /* + * Legacy uniform validation: only the length must be + * a multiple of mtd->erasesize. Address alignment is + * not checked here for backward compatibility. + */ + div_u64_rem(instr->len, mtd->erasesize, &rem); + if (rem) + return -EINVAL; + } } =20 addr =3D instr->addr; @@ -1877,34 +1974,12 @@ static int spi_nor_erase(struct mtd_info *mtd, stru= ct erase_info *instr) * to use "small sector erase", but that's not always optimal. */ =20 - /* "sector"-at-a-time erase */ - } else if (spi_nor_has_uniform_erase(nor) && - !spi_nor_has_multiple_uniform_erase_sizes(nor)) { - while (len) { - ret =3D spi_nor_lock_device(nor); - if (ret) - goto erase_err; - - ret =3D spi_nor_write_enable(nor); - if (ret) { - spi_nor_unlock_device(nor); - goto erase_err; - } - - ret =3D spi_nor_erase_sector(nor, addr); - spi_nor_unlock_device(nor); - if (ret) - goto erase_err; - - ret =3D spi_nor_wait_till_ready(nor); - if (ret) - goto erase_err; - - addr +=3D mtd->erasesize; - len -=3D mtd->erasesize; - } + } else if (spi_nor_has_uniform_erase(nor)) { + ret =3D spi_nor_erase_uniform(nor, addr, len); + if (ret) + goto erase_err; =20 - /* erase multiple sectors */ + /* erase multiple sectors on non-uniform flashes */ } else { ret =3D spi_nor_erase_multi_sectors(nor, addr, len); if (ret) @@ -2739,12 +2814,11 @@ static int spi_nor_select_erase(struct spi_nor *nor) * So to be backward compatible, the new implementation also tries to * manage the SPI flash memory as uniform with a single erase sector * size, when possible. - * Exception is when CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is enabled. - * We use multiple erase sizes for the SPI flash memory, but we still - * need to select one of the supported erase sizes to set - * mtd->erasesize. The selected erase size will be exposed as the - * uniform erase size but all other erase sizes are still available - * for use. + * + * When CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is enabled, multiple erase + * sizes are used internally, but mtd->erasesize still reflects the + * selected uniform erase size exposed to userspace. The other erase + * sizes remain available in the uniform region erase mask. */ if (spi_nor_has_uniform_erase(nor)) { erase =3D spi_nor_select_uniform_erase(map); --=20 2.43.0 From nobody Sat Jul 25 02:13:44 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B53E1437458 for ; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; cv=none; b=NKX1tc5tF/uX/KfJsI12eiKE30PO16Wjp76mmGXsGixjv2QQZuTPWYV8abZEUQ4j2Kdaga4sovq7ekmP3DLq0J1EYAehZ0LxJ9hWHmrPdoP1/ExwYpz4IltBHgdwex9QBiEcsbu5nUJnxzu/WCZ/Fn2RovPRKqN7ewweRlMIltg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784560424; c=relaxed/simple; bh=habTF8okM0Zg3QpK/5bI+/p5G+6G/orapw6aQ+0Ip2U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=fJN8rdgX2/Q9EpzUdVQoJsDfbaQvayoQfgCOVO2enm93S2islFkIgWhVXwi8h7JG2DrTZpOHeTj/oRs7c640F5quiPkI6pUMC2HXFTD0HZd9ylw1G574WCfJxDvBKRD6N73zHmlEUsd/ltrVscnkU+V8+GRbNKUEN7pCPVvGR5I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Vxj7z1bK; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Vxj7z1bK" Received: by smtp.kernel.org (Postfix) with ESMTPS id 5CEFBC2BCFB; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784560424; bh=habTF8okM0Zg3QpK/5bI+/p5G+6G/orapw6aQ+0Ip2U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Vxj7z1bK+QWGEiTuQ0p3VvO3iG9BCW2rNPRccS46q8S+bw5PJK9FG5aPZzXq+1yy+ ltnHf5Ilsq9fbon3KrE1Q+cvX9595r0B8ahmLD0mtr3N8AH/PhzXQSTMb2rLIiVrtm zBt28eGKX+6HwS8hQoApzSX3SJnzkIlt5FUNDqFdWIxauTb9Uul5M/7Vlxj097BEM+ +QKUDwoRh4pjOKWzxAjDhNJbgqHvcuMp1NOwF0q05RZEGr2hwGTIgqsWBf2nQG7DUZ m3pDNZ3W8qa4g82TnF2CxRB9Zby/thkofHjrkYt0mJzMtyBAvoBEauyJhEYCRde+hi P6QfOJbMG91IA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 41C87C44515; Mon, 20 Jul 2026 15:13:44 +0000 (UTC) From: Mateusz Litwin via B4 Relay Date: Mon, 20 Jul 2026 17:12:40 +0200 Subject: [PATCH RFC 3/3] mtd: spi-nor: skip erase type search in uniform bulk erase Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-spi_nor_multisize_erase-v1-3-38d719bfe77b@nokia.com> References: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> In-Reply-To: <20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b@nokia.com> To: Pratyush Yadav , Michael Walle , Takahiro Kuwano , Miquel Raynal , Richard Weinberger , Vignesh Raghavendra Cc: linux-mtd@lists.infradead.org, linux-kernel@vger.kernel.org, Mateusz Litwin X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784560422; l=2899; i=mateusz.litwin@nokia.com; s=20251217; h=from:subject:message-id; bh=JccJSJOGyqps8s+o8d+1DpyuMp97oOREt9SAuUTfY14=; b=lbQ4Fmof4KLiT5h9lWgbbmm1rJJWMIM8tx9ke27GMzCKVFiY+AqQey+s0Lf2iCgYuCg2nhF5u d0ntNlWgMC2BDwYUNuZg0qrbs2FqQRLPCqJ7yQmGOWJKm3s6Dzwwt6g X-Developer-Key: i=mateusz.litwin@nokia.com; a=ed25519; pk=9NV76cwWrtwYUektOrK/ht9GTzmhtkqSvghr3Td4hM4= X-Endpoint-Received: by B4 Relay for mateusz.litwin@nokia.com/20251217 with auth_id=579 X-Original-From: Mateusz Litwin Reply-To: mateusz.litwin@nokia.com From: Mateusz Litwin spi_nor_erase_uniform() calls spi_nor_find_best_erase_type() on every erase step when CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is enabled. For large requests whose middle is aligned to the largest supported erase size, that lookup always returns the same type. Find the largest supported uniform erase type once at the start of the request, then use it directly while the remaining length is still large enough and the current address stays aligned. Fall back to spi_nor_find_best_erase_type() only at the head or tail of the range, where a smaller erase type may be required. This has no effect on erase behavior; it only avoids redundant type selection work in the bulk-aligned case. Signed-off-by: Mateusz Litwin --- drivers/mtd/spi-nor/core.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index 4e13919d238a..31839dc2705d 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1689,8 +1689,8 @@ static int spi_nor_erase_uniform(struct spi_nor *nor,= u64 addr, u32 len) { const struct spi_nor_erase_map *map =3D &nor->params->erase_map; const struct spi_nor_erase_region *region =3D &map->uniform_region; - const struct spi_nor_erase_type *erase; - int ret; + const struct spi_nor_erase_type *erase, *largest =3D NULL; + int i, ret; /* * When CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE is disabled, erase_size is * mtd->erasesize and nor->erase_opcode was set once at init by @@ -1698,10 +1698,39 @@ static int spi_nor_erase_uniform(struct spi_nor *no= r, u64 addr, u32 len) */ u32 erase_size =3D nor->mtd.erasesize; =20 + if (IS_ENABLED(CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE)) { + u8 erase_mask =3D map->uniform_region.erase_mask; + + for (i =3D SNOR_ERASE_TYPE_MAX - 1; i >=3D 0; i--) { + if (!(erase_mask & BIT(i))) + continue; + if (!map->erase_type[i].size) + continue; + + largest =3D &map->erase_type[i]; + break; + } + + if (unlikely(!largest)) + return -EINVAL; + } + while (len) { if (IS_ENABLED(CONFIG_MTD_SPI_NOR_MULTI_ERASE_SIZE)) { - erase =3D spi_nor_find_best_erase_type(map, region, addr, - len); + /* + * For large aligned ranges, every step in the middle + * picks the same largest erase type. Skip + * spi_nor_find_best_erase_type() there and use + * @largest directly when @addr is aligned to it and + * @len is still large enough. Fall back to the full + * search only at the head or tail, where a smaller + * erase type may be required. + */ + if (len >=3D largest->size && !(addr & largest->size_mask)) + erase =3D largest; + else + erase =3D spi_nor_find_best_erase_type(map, region, + addr, len); if (unlikely(!erase)) return -EINVAL; =20 --=20 2.43.0