Skip to main content

TECH VEDA

Corporate on-site training - Submit proposal Pick your modulesSharpen your kernel skills: deep dives, drivers, Yocto, CVEs, careers — updated daily. Read the blog →
Kernel Features

Two NAND Parts, One Kernel Image: Which Wear-Levelling Threshold Applies?

Linux 7.3 adds a per-device UBI wear-levelling threshold. How to set and confirm it, which kernels reject it, and a problem in the kernel example.

Two NAND Parts, One Kernel Image: Which Wear-Levelling Threshold Applies?

Linux 7.3 allows each UBI device to have its own wear-levelling threshold, set at attach time. Earlier kernels compile in one value for every flash part. The value is the seventh field of the ubi.mtd= parameter and a new field in the attach ioctl. This matters to teams that use one kernel image for boards with different raw flash parts. No longterm kernel older than 7.3 has it.

A product family often uses one kernel image for several board variants. Those variants do not always have the same raw flash. One has SLC NAND rated for 100,000 erase cycles. Another has a cheaper part rated for far fewer. UBI distributes erases across each device. The wear-levelling threshold is the erase-count difference at which UBI starts to move static data to balance wear. Our earlier article on UBI on raw NAND described that value as a single build-time setting. This article explains the 7.3 change and its limits. It also reports two results from reading the code: how kernels before 7.3 respond to the new field, and a problem in the two-device example given in the kernel source. If every flash part in your products is rated for 100,000 cycles or more and you use the default, nothing changes for you.

What teams do today with one wear-levelling threshold

Before 7.3, the only control was a Kconfig symbol:

config MTD_UBI_WL_THRESHOLD
	int "UBI wear-leveling threshold"
	default 4096
	range 2 65536

Its help text says the default suits SLC NAND, NOR and other flash with an eraseblock life of 100,000 cycles or more. For parts with a life below 10,000 cycles, it recommends a lower value, for example 128 or 256. The value is compiled into the UBI code, so every UBI device on the system uses it.

A team with mixed flash parts has three choices. Each has a cost. First, it can build one kernel per board variant, which adds more images to test and sign. Second, it can set the low value everywhere. UBI then moves data on the durable part much earlier than that part needs, and every move needs an erase and a copy. Third, it can maintain a local patch to UBI. The commit message describes the same situation. One global threshold does not suit a kernel image that must support several NAND parts with different erase lifetimes.

What Linux 7.3 added

The commit adds one value, wl_threshold, to the two interfaces that attach an MTD device to UBI. No new Kconfig symbol is added. The existing symbol now sets the default.

The first interface is the ubi.mtd= parameter. It is used on the kernel command line when UBI is built in, or as mtd= when the module is loaded. It now has a seventh comma-separated field. The parameter description in drivers/mtd/ubi/build.c is now:

mtd=<name|num|path>[,<vid_hdr_offs>[,max_beb_per1024[,ubi_num[,enable_fm[,need_resv_pool[,wl_threshold]]]]]]

The second interface is the UBI_IOCATT ioctl on the UBI control device, which ubiattach uses. Its request structure had eight bytes of padding. Four of them now contain the new field:

struct ubi_attach_req {
	__s32 ubi_num;
	__s32 mtd_num;
	__s32 vid_hdr_offset;
	__s16 max_beb_per1024;
	__s8 disable_fm;
	__s8 need_resv_pool;
	__s32 wl_threshold;
	__s8 padding[4];
};

In both interfaces, 0 means “use CONFIG_MTD_UBI_WL_THRESHOLD“. The accepted range is 2 to 65536, the same as the Kconfig symbol. The structure size does not change.

How the wear-levelling threshold works per device

UBI keeps an erase counter for every physical eraseblock. The function ensure_wear_leveling() in wl.c decides when to act. It compares the lowest erase counter among used blocks with a high erase counter among free blocks. On kernels built with fastmap support, need_wear_leveling() in fastmap-wl.c does this comparison. When the difference reaches the wear-levelling threshold, UBI schedules a worker. The worker copies data from the low-counter block to the high-counter block. The block that held static data is then free for new writes.

Until 7.3, that comparison used a macro, UBI_WL_THRESHOLD, defined as the Kconfig value. A second macro, WL_FREE_MAX_DIFF, was twice the threshold and limited the choice of target. A free block was not chosen if its counter was above the lowest free counter plus that difference.

The commit deletes both macros, adds two fields to struct ubi_device, and sets them at attach time:

	ubi->wl_threshold = wl_threshold;
	ubi->wl_free_max_diff = wl_threshold * 2;

Every comparison in wl.c and fastmap-wl.c now uses these fields. The range check is in ubi_attach_mtd_dev(), the single function that both interfaces call, so both enforce the same limits. Nothing is written to flash. The threshold exists only in the memory of the attached device.

Try it

Only UBI itself needs to be enabled. To check whether a kernel source tree has the feature, search the UAPI header. The command prints matching lines on a 7.3 tree and nothing on a 7.2 tree:

raghu@techveda.org:~$ grep -n wl_threshold include/uapi/mtd/ubi-user.h

For a board with two flash parts, specify the wear-levelling threshold as the seventh field of each ubi.mtd= entry on the kernel command line:

ubi.mtd=0,0,0,0,0,0,256 ubi.mtd=1,0,0,1,0,0,4096

A field cannot be skipped by leaving it empty, so all six fields before the seventh must be written. A 0 in fields two, three, five and six gives the same result as leaving them out. When each variant has only one flash part, the value goes where that variant’s command line is built, for example the U-Boot environment.

The two-device example in the kernel source uses ubi_num 0 twice

Check the fourth field carefully. It is ubi_num, and a 0 written there is a request for UBI device 0, not a default. The example in the commit message, repeated as “Example 6” in the parameter description, is:

mtd=/dev/mtd0,0,0,0,0,0,256 mtd=/dev/mtd1,0,0,0,0,0,4096

Both entries ask for device 0. From our reading of ubi_attach_mtd_dev(), the second attach then fails with -EEXIST and the message “ubi0 already exists”. With UBI built in, the first device stays attached and the second is skipped. With UBI as a module, the first device is detached too and the module load fails. Give each device its own number as shown above, or write -1 for automatic numbering. The example text is unchanged in v7.4-rc2.

Confirm the value

The only confirmation on a running kernel is the attach message. Its format string is:

"max/mean erase counter: %d/%d, WL threshold: %d, image sequence number: %u"

Check it on the target:

root@imx6ull-evk:~# dmesg | grep "WL threshold"

Each attached device prints one line with the prefix ubi0:, ubi1: and so on. The number after “WL threshold:” is the value that device uses.

A device with no volume in use can be attached again at run time. The ubiattach tool now has a -w (--wl-threshold) option for this:

root@imx6ull-evk:~# ubidetach -m 1
root@imx6ull-evk:~# ubiattach -m 1 -w 256
root@imx6ull-evk:~# dmesg | grep "WL threshold"

Always read the attach message after this step. On a kernel older than 7.3, the four bytes are still padding that the attach ioctl never reads. The attach succeeds, and the device uses the Kconfig value without warning.

Which kernels have it

The commit first appears in v7.3-rc1 and was released in Linux 7.3, tagged on 18 October 2026. We checked the UAPI header in the stable trees on 12 November 2026. The field is present in 7.3.4. It is absent from 7.2.15, which has reached end of life, and from the current longterm releases 6.18.63, 6.12.117, 6.6.162, 6.1.192, 5.15.225 and 5.10.274. We found no plan for a backport.

KernelSeventh ubi.mtd= fieldubiattach -w
7.3 and laterAcceptedAccepted
7.2 and the longterm seriesRejected: “too many arguments”Attach succeeds, value ignored

A team that uses a vendor BSP based on 6.12 or 6.6 does not have the seventh field. Older kernels reject a seven-field entry; we checked 7.2.15, 6.12.117, 6.6.162 and 5.10.274. With UBI built in, that device is not attached, so a root filesystem on it will not mount. With UBI as a module, the module fails to load. Do not add the field to shared boot scripts until every kernel that reads them is 7.3 or later.

Limits

  • Attach time only. There is no sysfs attribute to read or change the value, and it is not stored on flash. Every attach, including those in recovery and factory scripts, must pass it again.
  • Device tree attach is not supported. An MTD partition marked with the linux,ubi compatible string is attached by a notifier that passes 0, so it always gets the Kconfig value.
  • An invalid value makes the attach fail. A non-zero value outside 2 to 65536 fails with “bad wear-leveling threshold”. The result is the same as for the example above.
  • Released tools lack the option. The -w option is in the mtd-utils master branch but not in the v2.3.1 tag, the most recent tag we found.
  • No guidance on values. The only values in the kernel tree are those in the Kconfig help text. The attach message also prints the maximum and mean erase counters, so record it on aged units to see the spread before you change the value.

The change has no architecture-specific code and no new Kconfig dependency, so it works on 32-bit Arm. It does not interact with PREEMPT_RT.

What was checked and what could not be verified

We read the commit and its diff, the UBI sources and Kconfig entry at v7.3, the kernel parameter code, the review thread on the linux-mtd list, and the mtd-utils patch and master branch. We checked each stable series for the field in the trees at the tags named above.

Nothing here was run on hardware. The failure of the two-device example, the behaviour of older kernels with the new field, and the boot behaviour after a rejected value come from reading the source, not from tests. So we show no captured log output. We have not reported the example to the linux-mtd list. We found no measurement, in the commit or the thread, of how different thresholds affect flash lifetime.

Key takeaways

  • From Linux 7.3, the wear-levelling threshold is set per UBI device at attach time; 0 selects the Kconfig value.
  • Use the seventh field of ubi.mtd= at boot, or ubiattach -w from a current mtd-utils build.
  • Give each device a distinct ubi_num. Example 6 in the parameter description writes 0 for both.
  • The attach message is the only way to confirm the value in use.
  • Device tree attach and all longterm kernels before 7.3 still use the Kconfig value.
Was this worth your time?

Frequently asked questions

Which kernel version added the per-device wear-levelling threshold?

Linux 7.3, tagged on 18 October 2026. The commit first appears in v7.3-rc1 and is absent from 7.2.15 and from the longterm series up to 6.18.

How do I confirm which wear-levelling threshold a UBI device is using?

Read the attach message in the kernel log. It prints the label WL threshold followed by the value for that device. There is no sysfs attribute for it.

Is the wear-levelling threshold stored on the flash?

No. It exists only in the memory of the attached device. Every attach must pass it again, otherwise the Kconfig value is used.

References

RB
Raghu Bharadwaj

Founder, TECH VEDA — 20+ years teaching the Linux kernel, device drivers and embedded systems.

Follow on LinkedIn

Get new posts by email

Kernel, embedded Linux and AI-era engineering — a few sharp reads a month. No spam.

We email occasionally and never share your address.