Before you start#
Review block storage, durability, and filesystem recovery. The source baseline is upstream v6.12. This chapter makes no writes to real flash; actual flash erase, UBI attachment, fault and recovery experiments require disposable guest emulation or dedicated recoverable hardware.
Learning goals#
Distinguish raw-flash eraseblocks from block sectors, trace UBI attachment and reference-sensitive detach, and reason about a rejected attach without equating it to filesystem corruption.
Source walkthrough#
- MTD raw media
Erase geometry, ECC and bad physical blocks
- UBI attach
Validate, reconstruct mapping and account for reserves
- LEB to PEB
Wear management can change the physical location
- UBIFS
Buffered writes, journal and index have separate durability stages
Raw NAND and NOR flash have erase and write constraints. Erasure happens in relatively large eraseblocks; some flash may have bad blocks and finite endurance. A conventional SSD normally exposes a block interface and performs substantial translation internally. MTD instead provides abstractions for supported raw memory technology devices; do not assume an MTD number is an ordinary disk partition.
UBI sits on a suitable MTD device and manages physical eraseblocks, wear and logical volumes. UBIFS uses UBI volumes and is distinct from formatting an arbitrary block device. A logical eraseblock is not a permanent physical address: UBI may move the mapping. A correct explanation of durability has to include the flash geometry, controller behavior, UBI, filesystem and application synchronization. The UBIFS documentation describes intended use and limitations.
In drivers/mtd/ubi/build.c, ubi_attach_mtd_dev validates inputs before creating an attachment. This exact excerpt is v6.12, GPL-2.0-or-later, copyright IBM and contributors; the surrounding loop and diagnostic are omitted:
if (ubi && mtd->index == ubi->mtd->index) {
pr_err("ubi: mtd%d is already attached to ubi%d\n",
mtd->index, i);
return -EEXIST;
}
Inside a loop over UBI slots, ubi may be null. A populated slot whose attached MTD index matches the requested device is a duplicate, so the function emits a diagnostic and returns -EEXIST. Its comment states that device creation and deletion are serialized by the caller; this loop does not acquire ubi_devices_lock itself. A caller must not turn that assumption into a general claim that all UBI data requires no locking.
Attachment, detachment, and a failure case#
Suppose an imagined MTD device with index 3 is already attached as UBI slot 0. A second attempt to attach that same index reaches the loop and gets -EEXIST; it does not create a second UBI attachment through this path. An attempt to attach a distinct device passes this specific duplicate check but can still fail on geometry, type, memory allocation, bad-block conditions or later setup. The same function rejects an MTD device emulated on a UBI volume to avoid problematic recursion and rejects unsupported MLC NAND unless the specified SLC emulation condition is met. The code explains why those checks exist; hardware-specific flash constraints remain essential.
On detachment, ubi_detach_mtd_dev obtains a device reference, checks its reference count while holding ubi_devices_lock, and normally returns -EBUSY if other references remain. Its anyway argument changes the treatment of a nonzero count; the source labels that situation as a bug. When the normal checks allow removal, the device is marked dead and removed from the device array with notifications and cleanup. The exact teardown has further steps in source. A busy result means some user still holds a reference; it does not by itself prove flash media corruption.
In the same build.c, v6.12, SPDX GPL-2.0-or-later (the function header and the teardown after this point omitted):
spin_lock(&ubi_devices_lock);
ubi->ref_count -= 1;
if (ubi->ref_count) {
if (!anyway) {
spin_unlock(&ubi_devices_lock);
return -EBUSY;
}
/* This may only happen if there is a bug */
ubi_err(ubi, "%s reference count %d, destroy anyway",
ubi->ubi_name, ubi->ref_count);
}
ubi->is_dead = true;
spin_unlock(&ubi_devices_lock);
The decrement happens before the test, so -EBUSY reports that other references remained after this call dropped its own. The anyway branch only logs the count and continues, and the source's comment calls that situation a bug, so the flag does not make a busy device safe to detach.
- Duplicate attach: -EEXIST
- Same MTD index already in an occupied slot; returned before any allocation
- Later attach failure
- Geometry, allocation, ubi_attach, uif, debugfs or thread errors take a cleanup label
- Detach while busy: -EBUSY
- Other references remain after the decrement; the device is not marked dead
A power cut during writing presents a separate question from duplicate attachment. Application writes, filesystem journal recovery and media ordering all matter. Neither an in-memory model of UBI slots nor a successful mount proves that every application update is durable. To investigate crash behavior, use a disposable guest and throwaway emulated image, record exact tool versions and configuration, and retain a recoverable copy; this source-only exercise deliberately does not perform that destructive test.
Raw media and the MTD contract#
NOR and NAND are families of nonvolatile memory with different access and erase characteristics. NAND commonly organizes storage into pages within eraseblocks. Programming and erasing have device-specific restrictions, and erasure resets an entire block rather than an arbitrary byte. The out-of-band (OOB) area can store metadata such as error-correction information or bad-block markers, subject to the controller and layout. Error-correcting codes (ECC) detect and often correct a limited number of bit errors; a corrected read can still be a signal of wear, while an uncorrectable error means the data cannot be trusted. Precise ECC strength, placement, geometry and bad-block handling depend on the chip and controller.
A factory-marked bad block is not available for normal data use, and additional blocks can fail during the device's life. Erase and program cycles wear cells, so a design must distribute use and reserve space. An SSD or eMMC typically exposes logical block addresses behind a flash translation layer (FTL), which internally manages much of this physical complexity. That abstraction does not make power-loss ordering or write durability automatic, but it is materially different from an MTD client directly managing raw-flash geometry. Do not assume a SATA or NVMe SSD should be attached to UBI just because it contains flash.
struct mtd_info represents an MTD device and describes properties including size, erase geometry and supported operations. MTD core mediates reads, writes, erases and other operations through the device's registered methods; drivers and controllers implement the hardware-specific details. A write request, an erase operation and a corrected read have different return paths. The interface and driver must preserve bounds and erase alignment where required. A driver that reports a completed command cannot by itself prove that a separate application-level transaction is durable. Start at mtdcore.c and the relevant MTD interface and NAND controller driver for the actual geometry and ECC reporting.
Mapping PEBs to LEBs#
UBI tracks physical eraseblocks (PEBs) on an MTD device and presents logical eraseblocks (LEBs) to volumes. A LEB's data may move between PEBs for wear leveling or recovery, so a logical number is not a permanent physical address. UBI stores metadata in each PEB; the usable LEB size is smaller than physical eraseblock size by layout-dependent overhead and alignment. UBI maintains pools and reserves for its own needs and possible bad blocks. The exact count of volume eraseblocks comes from the actual attached device and UBI's accounting, not a universal fixed subtraction.
Read the text version
- LEB 0: Logical eraseblock in a volume (fictional) → PEB 7: Physical eraseblock holding LEB 0 (fictional) (mapped now)
- LEB 1: Logical eraseblock in the same volume (fictional) → PEB 12: Physical eraseblock holding LEB 1 (fictional) (mapped now)
- LEB 1: Logical eraseblock in the same volume (fictional) → PEB 3: Possible destination after relocation (fictional) (possible after relocation)
For a worked capacity estimate, suppose a fictional NAND partition has 100 physical eraseblocks, each 128 KiB, and four are marked bad. Assume the chosen geometry leaves 124 KiB usable per good PEB after UBI headers and alignment. Before UBI's internal reserves and volume overhead, at most 96 times 124 KiB, or 11,904 KiB, remains for logical payload. If eight additional good PEBs are reserved for UBI and anticipated failures, the illustrative upper bound becomes 88 times 124 KiB, or 10,912 KiB. These reserve and overhead figures are assumptions for this exercise; they are not UBI defaults or a prediction for a real device. If a new bad PEB develops, the accounting and available reserve change; a wear-level relocation can change the PEB holding a particular LEB without changing the LEB number.
Volume update is not equivalent to appending a file. In drivers/mtd/ubi/upd.c, ubi_start_update marks the volume as updating, allocates an update buffer, writes an update marker, unmaps prior logical eraseblocks and flushes wear-leveling work. Errors propagate at several stages. The marker helps identify an interrupted update; it is not a generic atomic replacement of a filesystem tree. A zero-byte update has its own marker-clear and cleanup branch. A caller must handle a failed or interrupted update according to the UBI interface and recovery policy rather than assuming previous content survived unchanged. A separate atomic LEB change operation exists, with a different scope.
Attachment, cleanup and filesystem writing#
The duplicate and incompatible-device checks in ubi_attach_mtd_dev precede many later attach steps. On a successful attach, UBI initializes its in-memory device, reads or reconstructs on-flash state, establishes mappings and registers the device for use; failures during setup follow corresponding error labels and release resources acquired so far. The exact sequence is configuration-dependent, including fastmap support, so an audit of a particular failure must follow its labels and ownership rather than assume all cleanup happens at a single line. The normal detach path's reference check prevents ordinary removal while other users still hold the device. Forcibly treating a busy device as safe to detach would violate that lifetime requirement.
A more precise v6.12 attachment trace starts with slot selection and kzalloc of the ubi_device. After device initialization, io_init checks and establishes geometry, and peb_buf is allocated before ubi_attach reconstructs attachment state. The function then handles optional autoresize, initializes the user interface and debugfs, and creates the background thread. On success it enables that thread under wl_lock, publishes ubi_devices[ubi_num], notifies volume addition and returns the assigned number. If thread creation fails, execution reaches out_debugfs, then falls through out_uif, out_detach and out_free: the corresponding cleanup removes debugfs and user-interface state, closes wear leveling, frees volume and table state, frees temporary buffers and puts the initialized device. Earlier failures enter at the appropriate later label; for example, a failed io_init goes to out_free. The code's caller-side serialization assumption is essential when interpreting publication and duplicate checks.
In build.c, v6.12, SPDX GPL-2.0-or-later (the allocation and attach steps before these labels omitted):
out_debugfs:
ubi_debugfs_exit_dev(ubi);
out_uif:
uif_close(ubi);
out_detach:
ubi_wl_close(ubi);
ubi_free_all_volumes(ubi);
vfree(ubi->vtbl);
out_free:
vfree(ubi->peb_buf);
vfree(ubi->fm_buf);
put_device(&ubi->dev);
return err;
}
Entering at a label runs its cleanup and then falls through the labels below it, so an earlier failure releases fewer steps. The publication ubi_devices[ubi_num] = ubi happens only in the success path above these labels, so a failed attach is never visible through that slot.
- Callerubi_attach_mtd_dev
Attach an MTD device (index N)
- ubi_attach_mtd_devCaller
Return. -EEXIST if an occupied slot has the same index
- ubi_attach_mtd_devUBI device state
Allocate ubi_device; io_init; ubi_attach
- UBI device stateubi_attach_mtd_dev
Return. On error, goto an out_ label and release earlier steps
- ubi_attach_mtd_devUBI device state
Create background thread; enable under wl_lock
- ubi_attach_mtd_devCaller
Return. Return ubi_num after publishing ubi_devices[ubi_num]
Time runs downward. Dashed arrows show returns.
Read the text version
- Caller → ubi_attach_mtd_dev: Attach an MTD device (index N)
- ubi_attach_mtd_dev → Caller (return): -EEXIST if an occupied slot has the same index
- ubi_attach_mtd_dev → UBI device state: Allocate ubi_device; io_init; ubi_attach
- UBI device state → ubi_attach_mtd_dev (return): On error, goto an out_ label and release earlier steps
- ubi_attach_mtd_dev → UBI device state: Create background thread; enable under wl_lock
- ubi_attach_mtd_dev → Caller (return): Return ubi_num after publishing ubi_devices[ubi_num]
UBIFS maintains a journal and an index on top of UBI. Its journal records changes before they become fully reflected in the index; recovery and commit rules reconcile those structures after interruption. Its file operations also interact with the page cache and filesystem budgeting. “Journaled” does not mean every application write survives power loss without an appropriate synchronization operation or a functioning lower layer. UBI and UBIFS do not replace the application's protocol for ordering related files and directories.
One concrete write-side entry is ubifs_write_begin in fs/ubifs/file.c. The following exact v6.12 GPL-2.0-only excerpt, copyright Nokia and contributors, omits preceding initialization and following code:
if (unlikely(c->ro_error))
return -EROFS;
/* Try out the fast-path part first */
folio = __filemap_get_folio(mapping, index, FGP_WRITEBEGIN,
mapping_gfp_mask(mapping));
if (IS_ERR(folio))
return PTR_ERR(folio);
The first condition rejects a write when UBIFS has entered its read-only error state. Otherwise it requests a folio for the file mapping with write-begin flags and an allocation mask; failure returns the encoded error instead of dereferencing an error pointer. In the following code, a non-uptodate folio can skip a read for a complete folio overwrite or invoke do_readpage for a partial write. If that read fails, the function unlocks and puts the folio before returning the error. It then allocates filesystem budget, with further error handling. The excerpt shows preparation for a buffered write, not a completed flash program, committed journal, or successful fsync.
Consider a partial write to an uncached folio: existing bytes outside the requested range must be preserved, so the function tries to read them. If that read returns an error, the unlock and put path prevents holding the folio reference and lock indefinitely. If it is a full-folio overwrite, the code may avoid the read but budget conservatively because it has not established whether the page was previously present. That distinction is an optimization with correctness and space-accounting conditions, not proof that every write avoids reading flash. Follow UBIFS journal.c and synchronization paths when investigating a specific durability claim.
Read-only Linux observation lab#
On your own authorized host or a disposable guest, inspect MTD inventory only:
uname -r
cat /proc/mtd 2>/dev/null || echo 'MTD inventory unavailable'
for d in /sys/class/mtd/mtd[0-9]*; do
[ -d "$d" ] || continue
printf '%s\n' "$d"
for a in name type size erasesize writesize; do [ -r "$d/$a" ] && printf ' %s: %s\n' "$a" "$(cat "$d/$a")"; done
done
A /proc/mtd row reports a device number, size, erase size and name; sysfs can supply more attributes. These values describe the running machine's visible MTD devices, not automatically the ECC strength, bad-block reserve, UBI volume capacity or health. /proc/mtd may be absent or contain only a header on a PC or QEMU guest without a configured MTD device. Permission denial and missing sysfs attributes are also valid observations. Do not open MTD character devices, erase, attach UBI, format, mount or change anything for this inventory.
Source-only alternative: use the 100-PEB calculation and the ubifs_write_begin excerpt. Deliver the upper-bound arithmetic and a two-branch table: read-only error returns before folio acquisition; failed partial-folio read unlocks and releases the acquired folio. State explicitly that neither branch was run and that the example's reserved blocks are assumed. If a destructive flash experiment is later wanted, it requires a separately prepared disposable guest with an emulated MTD device and throwaway image; no such experiment is part of these commands.
Practice#
This unprivileged Python model checks only the duplicate-index rule and does not access storage:
python3 - <<'PY'
slots = {0: 3, 1: 7}
for requested in (3, 5, 7):
existing = next((slot for slot, mtd in slots.items() if mtd == requested), None)
print('mtd', requested, 'EEXIST at slot ' + str(existing) if existing is not None else 'passes duplicate check')
PY
Expected: 3 and 7 report their existing slots; 5 passes this one check. The counterexample is index 5: “passes duplicate check” is not “successfully attached.” Add a hypothetical remaining reference to slot 0 and explain why normal detach would report busy. Deliver the table with duplicate check, potential later failure and detach status in separate columns. This code creates no files and cannot erase a device.
Knowledge check#
- Why does the attach loop compare MTD indices and reject a match?
- What does a normal
-EBUSYon detach establish? - Is a logical eraseblock a fixed physical address for the lifetime of a volume?
- Why does a partial uncached folio write need a read that a complete overwrite can skip?
- Why is the calculated 10,912 KiB only a hypothetical upper bound?
Answers#
- It prevents attaching the same MTD device a second time; the documented caller serialization makes that check meaningful during device creation and deletion.
- Other references remain at the checked point. The result alone does not identify media corruption or guarantee which process holds a reference.
- No. UBI manages mappings and wear across physical eraseblocks. Investigate the specific mapping and recovery path rather than assuming fixed location.
- Bytes outside the changed range must be preserved. The cited path reads the existing folio and releases it on a read error; a complete overwrite can avoid that read under its stated condition.
- It assumes specific usable size, bad-block count and reserves. Real UBI layout, overhead, device health and volume accounting determine the actual result.
Checkpoint#
Submit the three-index table and one reference-sensitive detach timeline. Ready means -EEXIST, later attach errors and -EBUSY are distinguished, with no claim that the synthetic program exercised flash. Re-read the loop and detach lock region if not. This is a readiness check, not certification.