From f06711486c12c1fbb3ea9c0c595ca244b111891f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:42 -0700 Subject: [PATCH 01/10] ubifs: debug.h: fix kernel-doc struct prototypes Use the "struct" keyword to describe structs in kernel-doc format. This avoids kernel-doc warnings: Warning: fs/ubifs/debug.h:75 cannot understand function prototype: 'struct ubifs_debug_info' Warning: fs/ubifs/debug.h:130 cannot understand function prototype: 'struct ubifs_global_debug_info' Signed-off-by: Randy Dunlap Signed-off-by: Richard Weinberger --- fs/ubifs/debug.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ubifs/debug.h b/fs/ubifs/debug.h index d425861e6b82..48cd64eb2d01 100644 --- a/fs/ubifs/debug.h +++ b/fs/ubifs/debug.h @@ -26,7 +26,7 @@ typedef int (*dbg_znode_callback)(struct ubifs_info *c, #define UBIFS_DFS_DIR_LEN (3 + 1 + 2 + 3 + 1) /** - * ubifs_debug_info - per-FS debugging information. + * struct ubifs_debug_info - per-FS debugging information. * @old_zroot: old index root - used by 'dbg_check_old_index()' * @old_zroot_level: old index root level - used by 'dbg_check_old_index()' * @old_zroot_sqnum: old index root sqnum - used by 'dbg_check_old_index()' @@ -119,7 +119,7 @@ struct ubifs_debug_info { }; /** - * ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. + * struct ubifs_global_debug_info - global (not per-FS) UBIFS debugging information. * * @chk_gen: if general extra checks are enabled * @chk_index: if index xtra checks are enabled From abd6591fb5cc1490ab2cf5d9ff513a6677fa57b2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:43 -0700 Subject: [PATCH 02/10] ubifs: key.h: use correct function parameter name Use @hash as the function parameter name to prevent kernel-doc warnings: Warning: fs/ubifs/key.h:42 function parameter 'hash' not described in 'key_mask_hash' Warning: fs/ubifs/key.h:42 Excess function parameter 'val' description in 'key_mask_hash' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/key.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/key.h b/fs/ubifs/key.h index 8142d9d6fe5d..71497c1b4753 100644 --- a/fs/ubifs/key.h +++ b/fs/ubifs/key.h @@ -33,7 +33,7 @@ /** * key_mask_hash - mask a valid hash value. - * @val: value to be masked + * @hash: value to be masked * * We use hash values as offset in directories, so values %0 and %1 are * reserved for "." and "..". %2 is reserved for "end of readdir" marker. This From 4e5e02340a18183b0f8ad633da1516cad92ee43e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 10:20:44 -0700 Subject: [PATCH 03/10] ubifs: ubifs.h: clean up kernel-doc comments - use the struct keyword when describing a struct in kernel-doc format. - add or correct missing struct members @leaf, @eof, and @superblock_need_write. - add Returns: clauses for 4 function descriptions. to prevent kernel-doc warnings: Warning: fs/ubifs/ubifs.h:755 struct member 'leaf' not described in 'ubifs_zbranch' Warning: fs/ubifs/ubifs.h:814 struct member 'eof' not described in 'bu_info' Warning: fs/ubifs/ubifs.h:814 Excess struct member 'oef' description in 'bu_info' Warning: fs/ubifs/ubifs.h:992 cannot understand function prototype: 'struct ubifs_stats_info' Warning: fs/ubifs/ubifs.h:1513 struct member 'superblock_need_write' not described in 'ubifs_info' Warning: fs/ubifs/ubifs.h:1597 No description found for return value of 'ubifs_check_hash' Warning: fs/ubifs/ubifs.h:1612 No description found for return value of 'ubifs_check_hmac' Warning: fs/ubifs/ubifs.h:1653 No description found for return value of 'ubifs_branch_hash' Warning: fs/ubifs/ubifs.h:1703 No description found for return value of 'ubifs_auth_node_sz' Signed-off-by: Randy Dunlap Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/ubifs.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index b62a154c7bd4..00db0d19a85e 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -738,6 +738,7 @@ struct ubifs_jhead { * struct ubifs_zbranch - key/coordinate/length branch stored in znodes. * @key: key * @znode: znode address in memory + * @leaf: leaf node * @lnum: LEB number of the target node (indexing node or data node) * @offs: target node offset within @lnum * @len: target node length @@ -801,7 +802,7 @@ struct ubifs_znode { * @gc_seq: GC sequence number to detect races with GC * @cnt: number of data nodes for bulk read * @blk_cnt: number of data blocks including holes - * @oef: end of file reached + * @eof: end of file reached */ struct bu_info { union ubifs_key key; @@ -985,7 +986,7 @@ struct ubifs_budg_info { }; /** - * ubifs_stats_info - per-FS statistics information. + * struct ubifs_stats_info - per-FS statistics information. * @magic_errors: number of bad magic numbers (will be reset with a new mount). * @node_errors: number of bad nodes (will be reset with a new mount). * @crc_errors: number of bad crcs (will be reset with a new mount). @@ -1051,6 +1052,7 @@ struct ubifs_debug_info; * @rw_incompat: the media is not R/W compatible * @assert_action: action to take when a ubifs_assert() fails * @authenticated: flag indigating the FS is mounted in authenticated mode + * @superblock_need_write: superblock node needs to be written * * @tnc_mutex: protects the Tree Node Cache (TNC), @zroot, @cnext, @enext, and * @calc_idx_sz @@ -1588,8 +1590,9 @@ int ubifs_prepare_auth_node(struct ubifs_info *c, void *node, * @expected: first hash * @got: second hash * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hash(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1603,8 +1606,9 @@ static inline int ubifs_check_hash(const struct ubifs_info *c, * @expected: first HMAC * @got: second HMAC * - * Compare two hashes @expected and @got. Returns 0 when they are equal, a - * negative error code otherwise. + * Compare two hashes @expected and @got. + * + * Returns: 0 when they are equal, a negative error code otherwise. */ static inline int ubifs_check_hmac(const struct ubifs_info *c, const u8 *expected, const u8 *got) @@ -1644,7 +1648,7 @@ static inline void ubifs_exit_authentication(struct ubifs_info *c) * @c: UBIFS file-system description object * @br: branch to get the hash from * - * This returns a pointer to the hash of a branch. Since the key already is a + * Returns: a pointer to the hash of a branch. Since the key already is a * dynamically sized object we cannot use a struct member here. */ static inline u8 *ubifs_branch_hash(struct ubifs_info *c, @@ -1694,7 +1698,7 @@ static inline int ubifs_node_verify_hmac(const struct ubifs_info *c, * ubifs_auth_node_sz - returns the size of an authentication node * @c: UBIFS file-system description object * - * This function returns the size of an authentication node which can + * Returns: the size of an authentication node which can * be 0 for unauthenticated filesystems or the real size of an auth node * authentication is enabled. */ From 420477a84f1e52babfe60a43dbdd0988ed956845 Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:40 +0800 Subject: [PATCH 04/10] UBI: Preserve torture flag when rescheduling failed erasures In __erase_worker(), when ubi_sync_erase() returns -EINTR, -ENOMEM, -EAGAIN, or -EBUSY, the physical eraseblock is rescheduled for erasure via schedule_erase() with the torture flag hardcoded to 0. This unconditionally drops the torture request. If the error occurred before torture_peb() completed (e.g., -ENOMEM from the ec_hdr allocation in ubi_sync_erase(), errors from self_check_not_bad()/nor_erase_prepare() in ubi_io_sync_erase(), or torture_peb() failing mid-test because its internal do_sync_erase()/ ubi_io_read()/ubi_io_write() returned one of the above error codes), the torture test was never finished. Such a PEB may reach the free pool without being tortured, defeating the purpose of the torture test for detecting marginally-bad eraseblocks. If the error occurred after torture_peb() succeeded (the final do_sync_erase() in ubi_io_sync_erase() or ubi_io_write_ec_hdr() in ubi_sync_erase() failed), the torture has already been done and need not be repeated. Pass the torture flag by pointer to ubi_sync_erase() and ubi_io_sync_erase(), and clear it to zero once torture_peb() has completed successfully. __erase_worker() then forwards the updated wl_wrk->torture to schedule_erase(): torture already done is dropped, torture not completed is preserved across the reschedule. Fixes: 784c145444e7 ("UBI: fix error handling in erase worker") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/attach.c | 4 ++-- drivers/mtd/ubi/fastmap.c | 6 +++--- drivers/mtd/ubi/io.c | 11 +++++++---- drivers/mtd/ubi/ubi.h | 4 ++-- drivers/mtd/ubi/wl.c | 15 +++++++++------ 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index 0fa115cbf3ad..0ce7ff7400d3 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -771,7 +771,7 @@ void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av) static int early_erase_peb(struct ubi_device *ubi, const struct ubi_attach_info *ai, int pnum, int ec) { - int err; + int err, torture = 0; struct ubi_ec_hdr *ec_hdr; if ((long long)ec >= UBI_MAX_ERASECOUNTER) { @@ -790,7 +790,7 @@ static int early_erase_peb(struct ubi_device *ubi, ec_hdr->ec = cpu_to_be64(ec); - err = ubi_io_sync_erase(ubi, pnum, 0); + err = ubi_io_sync_erase(ubi, pnum, &torture); if (err < 0) goto out_free; diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 3bce1b4d8464..98cc698d9c34 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j; + int ret, i, j, torture = 0; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], 0); + ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1578,7 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], 0); + ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 915eb64cb001..d70d1313ad4e 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -519,8 +519,10 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) /** * ubi_io_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object - * @pnum: physical eraseblock number to erase - * @torture: if this physical eraseblock has to be tortured + * @pnum: the physical eraseblock number to erase + * @torture: if this physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function synchronously erases physical eraseblock @pnum. If @torture * flag is not zero, the physical eraseblock is checked by means of writing @@ -532,7 +534,7 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) * codes in case of other errors. Note, %-EIO means that the physical * eraseblock is bad. */ -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture) { int err, ret = 0; @@ -560,10 +562,11 @@ int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) return err; } - if (torture) { + if (*torture) { ret = torture_peb(ubi, pnum); if (ret < 0) return ret; + *torture = 0; } err = do_sync_erase(ubi, pnum); diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index af466cd83ae0..80b762892922 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -901,7 +901,7 @@ int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap, struct ubi_attach_info *ai_scan); /* wl.c */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture); +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture); int ubi_wl_get_peb(struct ubi_device *ubi); int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, int pnum, int torture); @@ -923,7 +923,7 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, int len); int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, int len); -int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture); +int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int *torture); int ubi_io_is_bad(const struct ubi_device *ubi, int pnum); int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum); int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index e3705db8e570..edfab98f7064 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -443,12 +443,14 @@ static int prot_queue_del(struct ubi_device *ubi, int pnum) * ubi_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object * @e: the physical eraseblock to erase - * @torture: if the physical eraseblock has to be tortured + * @torture: if the physical eraseblock has to be tortured; cleared to zero + * once the torture test has completed successfully so that a retry + * of the erase does not torture the physical eraseblock again * * This function returns zero in case of success and a negative error code in * case of failure. */ -int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int torture) +int ubi_sync_erase(struct ubi_device *ubi, struct ubi_wl_entry *e, int *torture) { int err; struct ubi_ec_hdr *ec_hdr; @@ -1113,7 +1115,7 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) dbg_wl("erase PEB %d EC %d LEB %d:%d", pnum, e->ec, wl_wrk->vol_id, wl_wrk->lnum); - err = ubi_sync_erase(ubi, e, wl_wrk->torture); + err = ubi_sync_erase(ubi, e, &wl_wrk->torture); if (!err) { spin_lock(&ubi->wl_lock); @@ -1150,7 +1152,8 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk) int err1; /* Re-schedule the LEB for erasure */ - err1 = schedule_erase(ubi, e, vol_id, lnum, 0, true); + err1 = schedule_erase(ubi, e, vol_id, lnum, wl_wrk->torture, + true); if (err1) { spin_lock(&ubi->wl_lock); wl_entry_destroy(ubi, e); @@ -1757,7 +1760,7 @@ static void shutdown_work(struct ubi_device *ubi) static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync) { struct ubi_wl_entry *e; - int err; + int err, torture = 0; e = kmem_cache_alloc(ubi_wl_entry_slab, GFP_KERNEL); if (!e) @@ -1768,7 +1771,7 @@ static int erase_aeb(struct ubi_device *ubi, struct ubi_ainf_peb *aeb, bool sync ubi->lookuptbl[e->pnum] = e; if (sync) { - err = ubi_sync_erase(ubi, e, false); + err = ubi_sync_erase(ubi, e, &torture); if (err) goto out_free; From be04fce9945aac2a843f5d16725391eb80d948ef Mon Sep 17 00:00:00 2001 From: Zhihao Cheng Date: Sat, 4 Jul 2026 16:30:41 +0800 Subject: [PATCH 05/10] UBI: fastmap: Pass to_be_tortured when reusing old fastmap PEBs In ubi_update_fastmap(), when no fresh PEB is available to replace an old fastmap PEB, the old PEB is reused by erasing it synchronously via ubi_sync_erase() with the torture flag hardcoded to zero. However, old_fm->to_be_tortured[] may be non-zero: it is set during fastmap scanning when ubi_io_read_data()/ubi_io_read_ec_hdr() reports UBI_IO_BITFLIPS, meaning the PEB showed signs of bit decay and should be tortured to verify it is still reliable. When the old PEB is instead returned to the WL sub-system via ubi_wl_put_fm_peb(), the to_be_tortured flag is honored. The synchronous reuse path is inconsistent: it silently skips the torture test for PEBs that exhibited bit-flips. Fix it by passing &old_fm->to_be_tortured[i] to ubi_sync_erase(). Fixes: dbb7d2a88d2a ("UBI: Add fastmap core") Signed-off-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/fastmap.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c index 98cc698d9c34..3f9fb7c181ff 100644 --- a/drivers/mtd/ubi/fastmap.c +++ b/drivers/mtd/ubi/fastmap.c @@ -1488,7 +1488,7 @@ static void return_fm_pebs(struct ubi_device *ubi, */ int ubi_update_fastmap(struct ubi_device *ubi) { - int ret, i, j, torture = 0; + int ret, i, j; struct ubi_fastmap_layout *new_fm, *old_fm; struct ubi_wl_entry *tmp_e; @@ -1526,7 +1526,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (!tmp_e) { if (old_fm && old_fm->e[i]) { - ret = ubi_sync_erase(ubi, old_fm->e[i], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[i], + &old_fm->to_be_tortured[i]); if (ret < 0) { ubi_err(ubi, "could not erase old fastmap PEB"); @@ -1578,7 +1579,8 @@ int ubi_update_fastmap(struct ubi_device *ubi) if (old_fm) { /* no fresh anchor PEB was found, reuse the old one */ if (!tmp_e) { - ret = ubi_sync_erase(ubi, old_fm->e[0], &torture); + ret = ubi_sync_erase(ubi, old_fm->e[0], + &old_fm->to_be_tortured[0]); if (ret < 0) { ubi_err(ubi, "could not erase old anchor PEB"); From 95d27c1708bb6e8823c8e7c623f9abc2a91bf4bf Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Fri, 24 Jul 2026 09:43:27 +0200 Subject: [PATCH 06/10] ubifs: fix out-of-bounds read in signature length check ubifs_sb_verify_signature() bounds the on-disk ubifs_sig_node->len field before handing the signature payload to verify_pkcs7_signature(), but the check has the wrong sign: if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) The signature bytes start sizeof(struct ubifs_sig_node) (UBIFS_SIG_NODE_SZ, 64 bytes) into the node, so the payload is at most snod->len - sizeof(struct ubifs_sig_node) bytes long. Adding the header size instead of subtracting it accepts a declared length up to 2 * UBIFS_SIG_NODE_SZ larger than the node actually holds -- past the end of c->sbuf, which is vmalloc(c->leb_size). verify_pkcs7_signature() -> pkcs7_parse_message() -> asn1_ber_decoder() is then handed that inflated length and reads beyond the allocation while walking the DER headers. The node length comes straight from the mounted image, so a crafted signed UBIFS image reaches this via ubifs_read_superblock() before the signature is cryptographically checked. snod->len is guaranteed to be >= UBIFS_SIG_NODE_SZ by the node scanner (c->ranges[UBIFS_SIG_NODE].min_len == UBIFS_SIG_NODE_SZ), so the corrected subtraction cannot underflow. Legitimately signed images are unaffected: a correct superblock never declares a signature longer than the node it is embedded in. Fixes: 817aa094842d ("ubifs: support offline signed images") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Assisted-by: AuditCode-AI:2026.07 Reviewed-by: Richard Weinberger Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- fs/ubifs/auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c index a4a0158f712d..1f770795ee70 100644 --- a/fs/ubifs/auth.c +++ b/fs/ubifs/auth.c @@ -217,7 +217,7 @@ int ubifs_sb_verify_signature(struct ubifs_info *c, signode = snod->node; - if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) { + if (le32_to_cpu(signode->len) > snod->len - sizeof(struct ubifs_sig_node)) { ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len)); err = -EINVAL; goto out_destroy; From 5b0a6b554e12a97f9771a9a9f4ea1f5457373c73 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 8 Jun 2026 00:06:39 -0400 Subject: [PATCH 07/10] ubi: Fix rollback for explicit UBI device numbers ubi_init_attach() rolls back module initialization failures by scanning ubi_devices[0..i-1], where i is the mtd= parameter index. That assumes the parameter index matches the UBI device number. That assumption is not true when mtd= specifies an explicit ubi_num. A successfully attached device can be stored at a higher ubi_devices[] slot, and a later failure can miss it during rollback. Scan the full ubi_devices[] array and detach by the actual array index, matching the way UBI devices are stored. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Yuho Choi Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 674ad87809df..90c5c83d90e3 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1317,10 +1317,10 @@ static int __init ubi_init_attach(void) return 0; out_detach: - for (k = 0; k < i; k++) + for (k = 0; k < UBI_MAX_DEVICES; k++) if (ubi_devices[k]) { mutex_lock(&ubi_devices_mutex); - ubi_detach_mtd_dev(ubi_devices[k]->ubi_num, 1); + ubi_detach_mtd_dev(k, 1); mutex_unlock(&ubi_devices_mutex); } return err; From 31dd710cd84d5dd63c49f640d3a9f36c9699ca95 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 7 Aug 2026 16:45:50 -0400 Subject: [PATCH 08/10] mtd: ubi: Release device reference on busy detach ubi_detach_mtd_dev() obtains a device reference through ubi_get_device() before checking whether the UBI device is busy. The busy return path drops ubi->ref_count but leaves the device reference held, so the device object cannot be released after a later detach. Drop the device reference before returning -EBUSY. Fixes: 7e84c961b2eb ("mtd: ubi: introduce pre-removal notification for UBI volumes") Signed-off-by: Yuho Choi Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 90c5c83d90e3..1997bb70b702 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1105,6 +1105,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway) ubi->ref_count -= 1; if (ubi->ref_count) { if (!anyway) { + put_device(&ubi->dev); spin_unlock(&ubi_devices_lock); return -EBUSY; } From ff7937d14f5333ec7e6fb50d00a152d7974f4105 Mon Sep 17 00:00:00 2001 From: Ran Hongyun Date: Thu, 23 Jul 2026 10:02:32 +0800 Subject: [PATCH 09/10] UBI: fix two issues in the ubi.mtd MODULE_PARM_DESC Fix two issues introduced before: - The parameter format string was missing the enable_fm and need_resv_pool tokens introduced in an earlier commit. - The bad-block reservation note was misplaced after Example 5 instead of after Example 3. It was misplaced due to an earlier patch. Fixes: 83ff59a06663 ("UBI: support ubi_num on mtd.ubi command line") Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 1997bb70b702..456090a12f52 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -1575,7 +1575,7 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1588,9 +1588,9 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd= Date: Thu, 23 Jul 2026 10:02:33 +0800 Subject: [PATCH 10/10] UBI: support per-device wear-leveling threshold The UBI wear-leveling threshold (CONFIG_MTD_UBI_WL_THRESHOLD) is currently a compile-time constant shared by all UBI devices. When a single kernel image must support multiple NAND flashes with different erase lifetimes, one global threshold cannot suit all devices. Add a per-device configurable wl_threshold parameter: - UAPI: add __s32 wl_threshold to struct ubi_attach_req, carved from the existing padding. - Module parameter: ubi.mtd gains a new optional token "wl_threshold": ubi.mtd=0,0,0,0,0,0,256 ubi.mtd=1,0,0,0,0,0,4096 0 means "use the kernel default", the accepted range is 2-65536. Signed-off-by: Ran Hongyun Reviewed-by: Zhihao Cheng Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/build.c | 43 ++++++++++++++++++++++++++++++------ drivers/mtd/ubi/cdev.c | 2 +- drivers/mtd/ubi/fastmap-wl.c | 8 +++---- drivers/mtd/ubi/ubi.h | 21 +++++++++++++++++- drivers/mtd/ubi/wl.c | 39 ++++++++------------------------ include/uapi/mtd/ubi-user.h | 10 ++++++++- 6 files changed, 79 insertions(+), 44 deletions(-) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 456090a12f52..910e6b0ff5e7 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -36,7 +36,7 @@ #define MTD_PARAM_LEN_MAX 64 /* Maximum number of comma-separated items in the 'mtd=' parameter */ -#define MTD_PARAM_MAX_COUNT 6 +#define MTD_PARAM_MAX_COUNT 7 /* Maximum value for the number of bad PEBs per 1024 PEBs */ #define MAX_MTD_UBI_BEB_LIMIT 768 @@ -56,6 +56,7 @@ * @max_beb_per1024: maximum expected number of bad PEBs per 1024 PEBs * @enable_fm: enable fastmap when value is non-zero * @need_resv_pool: reserve pool->max_size pebs when value is none-zero + * @wl_threshold: wear-leveling threshold, 0 means use CONFIG_MTD_UBI_WL_THRESHOLD */ struct mtd_dev_param { char name[MTD_PARAM_LEN_MAX]; @@ -64,6 +65,7 @@ struct mtd_dev_param { int max_beb_per1024; int enable_fm; int need_resv_pool; + int wl_threshold; }; /* Numbers of elements set in the @mtd_dev_param array */ @@ -832,6 +834,8 @@ static int autoresize(struct ubi_device *ubi, int vol_id) * @max_beb_per1024: maximum expected number of bad PEB per 1024 PEBs * @disable_fm: whether disable fastmap * @need_resv_pool: whether reserve pebs to fill fm_pool + * @wl_threshold: wear-leveling threshold for this UBI device; 0 means use + * %CONFIG_MTD_UBI_WL_THRESHOLD; accepted range is 2-65536 * * This function attaches MTD device @mtd_dev to UBI and assign @ubi_num number * to the newly created UBI device, unless @ubi_num is %UBI_DEV_NUM_AUTO, in @@ -848,7 +852,7 @@ static int autoresize(struct ubi_device *ubi, int vol_id) */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, bool disable_fm, - bool need_resv_pool) + bool need_resv_pool, int wl_threshold) { struct ubi_device *ubi; int i, err; @@ -859,6 +863,15 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, if (!max_beb_per1024) max_beb_per1024 = CONFIG_MTD_UBI_BEB_LIMIT; + if (!wl_threshold) + wl_threshold = CONFIG_MTD_UBI_WL_THRESHOLD; + + if (wl_threshold < 2 || wl_threshold > 65536) { + pr_err("ubi: bad wear-leveling threshold %d\n", + wl_threshold); + return -EINVAL; + } + /* * Check if we already have the same MTD device attached. * @@ -944,6 +957,8 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->ubi_num = ubi_num; ubi->vid_hdr_offset = vid_hdr_offset; ubi->autoresize_vol_id = -1; + ubi->wl_threshold = wl_threshold; + ubi->wl_free_max_diff = wl_threshold * 2; #ifdef CONFIG_MTD_UBI_FASTMAP ubi->fm_pool.used = ubi->fm_pool.size = 0; @@ -1044,7 +1059,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, ubi->vol_count - UBI_INT_VOL_COUNT, UBI_INT_VOL_COUNT, ubi->vtbl_slots); ubi_msg(ubi, "max/mean erase counter: %d/%d, WL threshold: %d, image sequence number: %u", - ubi->max_ec, ubi->mean_ec, CONFIG_MTD_UBI_WL_THRESHOLD, + ubi->max_ec, ubi->mean_ec, ubi->wl_threshold, ubi->image_seq); ubi_msg(ubi, "available PEBs: %d, total reserved PEBs: %d, PEBs reserved for bad PEB handling: %d", ubi->avail_pebs, ubi->rsvd_pebs, ubi->beb_rsvd_pebs); @@ -1248,7 +1263,7 @@ static void ubi_notify_add(struct mtd_info *mtd) /* called while holding mtd_table_mutex */ mutex_lock_nested(&ubi_devices_mutex, SINGLE_DEPTH_NESTING); - err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false); + err = ubi_attach_mtd_dev(mtd, UBI_DEV_NUM_AUTO, 0, 0, false, false, 0); mutex_unlock(&ubi_devices_mutex); if (err < 0) __put_mtd_device(mtd); @@ -1290,7 +1305,8 @@ static int __init ubi_init_attach(void) err = ubi_attach_mtd_dev(mtd, p->ubi_num, p->vid_hdr_offs, p->max_beb_per1024, p->enable_fm == 0, - p->need_resv_pool != 0); + p->need_resv_pool != 0, + p->wl_threshold); mutex_unlock(&ubi_devices_mutex); if (err < 0) { pr_err("UBI error: cannot attach mtd%d\n", @@ -1570,12 +1586,24 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } else p->need_resv_pool = 0; + token = tokens[6]; + if (token) { + int err = kstrtoint(token, 10, &p->wl_threshold); + + if (err) { + pr_err("UBI error: bad value for wl_threshold parameter: %s\n", + token); + return -EINVAL; + } + } else + p->wl_threshold = 0; + mtd_devs += 1; return 0; } module_param_call(mtd, ubi_mtd_param_parse, NULL, NULL, 0400); -MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool]]]]].\n" +MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=[,[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool[,wl_threshold]]]]]].\n" "Multiple \"mtd\" parameters may be specified.\n" "MTD devices may be specified by their number, name, or path to the MTD character device node.\n" "Optional \"vid_hdr_offs\" parameter specifies UBI VID header position to be used by UBI. (default value if 0)\n" @@ -1590,7 +1618,8 @@ MODULE_PARM_DESC(mtd, "MTD devices to attach. Parameter format: mtd=free, WL_FREE_MAX_DIFF, + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, !can_fill_pools(ubi, left_free)); self_check_in_wl_tree(ubi, e, &ubi->free); rb_erase(&e->u.rb, &ubi->free); @@ -392,18 +392,18 @@ static bool need_wear_leveling(struct ubi_device *ubi) if (!e) { if (!ubi->free.rb_node) return false; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = e->ec; } else { ec = e->ec; if (ubi->free.rb_node) { - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); ec = max(ec, e->ec); } } e = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - return ec - e->ec >= UBI_WL_THRESHOLD; + return ec - e->ec >= ubi->wl_threshold; } /* get_peb_for_wl - returns a PEB to be used internally by the WL sub-system. diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 80b762892922..8a9ac60ff2f4 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -516,6 +516,22 @@ struct ubi_debug_info { * @bgt_thread: background thread description object * @thread_enabled: if the background thread is enabled * @bgt_name: background thread name + * @wl_threshold: Maximum difference between two erase counters. If this + * threshold is exceeded, the WL sub-system starts moving + * data from used physical eraseblocks with low erase + * counter to free physical eraseblocks with high erase counter. + * @wl_free_max_diff: When a physical eraseblock is moved, the WL sub-system + * has to pick the target physical eraseblock to move to. + * The simplest way would be just to pick the one with the + * highest erase counter. But in certain workloads this + * could lead to an unlimited wear of one or few physical + * eraseblock. Indeed, imagine a situation when the picked + * physical eraseblock is constantly erased after the + * data is written to it. So, we have a constant which + * limits the highest erase counter of the free physical + * eraseblock to pick. Namely, the WL sub-system does not + * pick eraseblocks with erase counter greater than the + * lowest erase counter plus @wl_free_max_diff. * * @flash_size: underlying MTD device size (in bytes) * @peb_count: count of physical eraseblocks on the MTD device @@ -623,6 +639,8 @@ struct ubi_device { struct task_struct *bgt_thread; int thread_enabled; char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + int wl_threshold; + int wl_free_max_diff; /* I/O sub-system's stuff */ long long flash_size; @@ -938,7 +956,8 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, /* build.c */ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset, int max_beb_per1024, - bool disable_fm, bool need_resv_pool); + bool disable_fm, bool need_resv_pool, + int wl_threshold); int ubi_detach_mtd_dev(int ubi_num, int anyway); struct ubi_device *ubi_get_device(int ubi_num); void ubi_put_device(struct ubi_device *ubi); diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index edfab98f7064..5a0db928a90e 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -95,27 +95,6 @@ /* Number of physical eraseblocks reserved for wear-leveling purposes */ #define WL_RESERVED_PEBS 1 -/* - * Maximum difference between two erase counters. If this threshold is - * exceeded, the WL sub-system starts moving data from used physical - * eraseblocks with low erase counter to free physical eraseblocks with high - * erase counter. - */ -#define UBI_WL_THRESHOLD CONFIG_MTD_UBI_WL_THRESHOLD - -/* - * When a physical eraseblock is moved, the WL sub-system has to pick the target - * physical eraseblock to move to. The simplest way would be just to pick the - * one with the highest erase counter. But in certain workloads this could lead - * to an unlimited wear of one or few physical eraseblock. Indeed, imagine a - * situation when the picked physical eraseblock is constantly erased after the - * data is written to it. So, we have a constant which limits the highest erase - * counter of the free physical eraseblock to pick. Namely, the WL sub-system - * does not pick eraseblocks with erase counter greater than the lowest erase - * counter plus %WL_FREE_MAX_DIFF. - */ -#define WL_FREE_MAX_DIFF (2*UBI_WL_THRESHOLD) - /* * Maximum number of consecutive background thread failures which is enough to * switch to read-only mode. @@ -358,7 +337,7 @@ static struct ubi_wl_entry *find_wl_entry(struct ubi_device *ubi, * * This function looks for a wear leveling entry with medium erase counter, * but not greater or equivalent than the lowest erase counter plus - * %WL_FREE_MAX_DIFF/2. + * @ubi->wl_free_max_diff/2. */ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, struct rb_root *root) @@ -368,7 +347,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, first = rb_entry(rb_first(root), struct ubi_wl_entry, u.rb); last = rb_entry(rb_last(root), struct ubi_wl_entry, u.rb); - if (last->ec - first->ec < WL_FREE_MAX_DIFF) { + if (last->ec - first->ec < ubi->wl_free_max_diff) { e = rb_entry(root->rb_node, struct ubi_wl_entry, u.rb); /* @@ -379,7 +358,7 @@ static struct ubi_wl_entry *find_mean_wl_entry(struct ubi_device *ubi, */ e = may_reserve_for_fm(ubi, e, root); } else - e = find_wl_entry(ubi, root, WL_FREE_MAX_DIFF/2, 0); + e = find_wl_entry(ubi, root, ubi->wl_free_max_diff/2, 0); return e; } @@ -708,7 +687,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, #ifdef CONFIG_MTD_UBI_FASTMAP e1 = find_anchor_wl_entry(&ubi->used); if (e1 && ubi->fm_anchor && - (ubi->fm_anchor->ec - e1->ec >= UBI_WL_THRESHOLD)) { + (ubi->fm_anchor->ec - e1->ec >= ubi->wl_threshold)) { ubi->fm_do_produce_anchor = 1; /* * fm_anchor is no longer considered a good anchor. @@ -745,7 +724,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, if (!e2) goto out_cancel; - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) { + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) { dbg_wl("no WL needed: min used EC %d, max free EC %d", e1->ec, e2->ec); @@ -1058,12 +1037,12 @@ static int ensure_wear_leveling(struct ubi_device *ubi, int nested) * We schedule wear-leveling only if the difference between the * lowest erase counter of used physical eraseblocks and a high * erase counter of free physical eraseblocks is greater than - * %UBI_WL_THRESHOLD. + * @ubi->wl_threshold. */ e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); - e2 = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e2 = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); - if (!(e2->ec - e1->ec >= UBI_WL_THRESHOLD)) + if (!(e2->ec - e1->ec >= ubi->wl_threshold)) goto out_unlock; #endif dbg_wl("schedule wear-leveling"); @@ -2093,7 +2072,7 @@ static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi) { struct ubi_wl_entry *e; - e = find_wl_entry(ubi, &ubi->free, WL_FREE_MAX_DIFF, 0); + e = find_wl_entry(ubi, &ubi->free, ubi->wl_free_max_diff, 0); self_check_in_wl_tree(ubi, e, &ubi->free); ubi->free_count--; ubi_assert(ubi->free_count >= 0); diff --git a/include/uapi/mtd/ubi-user.h b/include/uapi/mtd/ubi-user.h index aa872a41ffb9..3538e11b5175 100644 --- a/include/uapi/mtd/ubi-user.h +++ b/include/uapi/mtd/ubi-user.h @@ -289,6 +289,13 @@ enum { * If @disable_fm is not zero, ubi doesn't create new fastmap even the module * param 'fm_autoconvert' is set, and existed old fastmap will be destroyed * after doing full scanning. + * + * The @wl_threshold defines the maximum difference between the highest and the + * lowest erase counter value of eraseblocks of this UBI device. When this + * threshold is exceeded, UBI starts performing wear leveling by means of + * moving data from eraseblock with low erase counter to eraseblocks with high + * erase counter. If @wl_threshold is zero, the default kernel value of + * %CONFIG_MTD_UBI_WL_THRESHOLD is used. The accepted range is 2-65536. */ struct ubi_attach_req { __s32 ubi_num; @@ -297,7 +304,8 @@ struct ubi_attach_req { __s16 max_beb_per1024; __s8 disable_fm; __s8 need_resv_pool; - __s8 padding[8]; + __s32 wl_threshold; + __s8 padding[4]; }; /*