If you’ve ever wondered how an embedded Linux board boots from a raw flash chip instead of an SSD or SD card, the answer is the MTD subsystem — the piece of the kernel that this free linux kernel development course lecture walks you through end to end. Raw NOR and NAND flash don’t behave like a disk: they can’t be overwritten in place, they erase in large blocks, and they can develop bad blocks over their lifetime. MTD is the abstraction layer that hides all of that from the rest of the kernel and from user space.
What You Will Learn
- Why raw flash memory needs a dedicated kernel subsystem instead of the normal block layer
- The three-layer architecture of MTD: core, chip drivers, and user-facing device nodes
- The practical differences between NOR and NAND flash from a driver author’s point of view
- How the character (
/dev/mtdN) and block (/dev/mtdblockN) device nodes are numbered - How to build a tiny MTD-backed test device on a normal Linux machine and inspect it
Prerequisites
- Comfortable with basic Linux kernel module concepts (insmod, dmesg, /proc, /sys)
- A Linux machine (a VM is fine) running a reasonably current kernel, with
mtd-utilsinstallable - Basic familiarity with block vs. character devices is helpful but not required
Why Flash Memory Needs Its Own Subsystem
A hard disk or an SD card can be overwritten one sector at a time, and the controller inside the card quietly handles wear leveling and bad-block management for you. Raw NOR and NAND flash chips offer no such luxury to the host. Three physical constraints drive the entire design of MTD:
- Erase before write. A flash cell can only be written from a 1 to a 0. Turning it back to a 1 requires erasing an entire block, not a single byte.
- Large erase granularity. Erase blocks are typically tens to hundreds of kilobytes, so small updates mean read-modify-erase-write cycles at the block level.
- Bad blocks and wear. NAND flash in particular ships with some bad blocks from the factory and can develop more over its program/erase cycle lifetime; the software layer must track and avoid them.
Because none of this fits the normal block-device model that assumes any sector can be rewritten freely, the kernel needed a subsystem that speaks flash’s native language and only translates to something disk-like at the very top. That subsystem is MTD.
The Three-Layer MTD Architecture
MTD is best understood as a stack of three layers, each with a narrow, well-defined job. The bottom layer talks to silicon, the middle layer provides one uniform API regardless of chip type, and the top layer exposes that API to user space in familiar device-node form.
The MTD Core
The core sits in the middle and never talks to hardware directly. It exposes a single, chip-agnostic API — erase a region, read a range, write a range, mark bad blocks — and every chip driver underneath implements that same contract. This is exactly what makes the rest of the kernel (and file systems like JFFS2, UBIFS, or squashfs-on-MTD) able to ignore whether the flash underneath is NOR or NAND.
Chip Drivers
Below the core sit the chip drivers, split along NOR and NAND lines:
| Aspect | NOR chip drivers | NAND chip drivers |
|---|---|---|
| Access pattern | Byte-addressable, can often execute code in place (XIP) | Page-addressable only, no XIP |
| Driver count needed | Small — mostly one CFI-compliant driver covers the field | Large — one driver per NAND controller, usually supplied by the SoC/board vendor |
| Bad blocks | Rare, largely ignorable | Expected from the factory, must be tracked |
| Typical use today | Small bootloader/boot-config chips | Main storage on cost-sensitive embedded boards |
Because NOR chips are largely standardized around the Common Flash Interface (CFI), a handful of generic drivers cover almost every NOR part on the market. NAND is the opposite: the controller that drives the NAND bus is usually SoC-specific, so the kernel carries dozens of separate NAND controller drivers, one per family of hardware.
User-Level Device Nodes
At the top of the stack, MTD exposes two families of device nodes so both raw and filesystem-style access are possible:
| Device type | Major number | Node pattern | Purpose |
|---|---|---|---|
| Character device | 90 | /dev/mtdN and /dev/mtdNro | Raw erase/read/write access, used by mtd-utils and flashing tools |
| Block device | 31 | /dev/mtdblockN | Presents flash as a block device, mainly for read-only filesystems like squashfs |
Each MTD partition number N gets two character nodes: a read-write one at minor number N*2 and a read-only mirror at minor number N*2 + 1. The block device path exists mainly so that filesystems designed for ordinary block devices can sit on top of flash, even though the flash still doesn’t support arbitrary in-place rewrites underneath.
Hands-On: Building a Test MTD Device
You don’t need real flash hardware to explore MTD. The kernel ships mtdram, a driver that carves an MTD device out of ordinary RAM, which is perfect for seeing the layers in action on a desktop or VM.
# Install the userspace tools first
sudo apt install mtd-utils
# Load a 4 MiB RAM-backed MTD device with a 64 KiB erase block
sudo modprobe mtdram total_size=4096 erase_size=64
# Confirm the kernel created it
cat /proc/mtd
Expected output:
dev: size erasesize name
mtd0: 00400000 00010000 "mtdram test device"
Now inspect it the same way you would inspect real NAND on a board:
sudo mtdinfo /dev/mtd0
Expected output:
mtd0
Name: mtdram test device
Type: ram
Eraseblock size: 65536 bytes, 64.0 KiB
Amount of eraseblocks: 64 (4194304 bytes, 4.0 MiB)
Minimum input/output unit size: 1 bytes
Sub-page size: 1 bytes
Character device major/minor: 90:0
Bad blocks are allowed: false
Device is writable: true
Then exercise the core’s erase/read/write API directly with mtd-utils:
# Erase the whole device
sudo flash_erase /dev/mtd0 0 0
# Write a test file into it
echo "hello from ep_mtd_demo" | sudo dd of=/tmp/payload.bin bs=1 count=23
sudo nandwrite -p /dev/mtd0 /tmp/payload.bin 2>/dev/null || sudo dd if=/tmp/payload.bin of=/dev/mtd0 bs=1
# Read it back through the raw char device
sudo dd if=/dev/mtd0 bs=1 count=23 2>/dev/null
Expected output:
hello from ep_mtd_demo
Everything you just did — erase, write, read — went through the exact three-layer path from the diagram above: the char device node, into the MTD core, into the (in this case RAM-backed) chip driver.
Watching MTD Devices Register: A Tiny Notifier Module
To see the core layer at work from kernel space, here’s a small original module, ep_mtd_notify, that registers an MTD notifier and logs every MTD device as it’s added or removed — useful when you’re bringing up a new NAND controller driver and want to confirm the core sees your partitions correctly.
// ep_mtd_notify.c
#include <linux/module.h>
#include <linux/mtd/mtd.h>
static void ep_mtd_added(struct mtd_info *mtd)
{
pr_info("ep_mtd_notify: added mtd%d \"%s\" size=%llu\n",
mtd->index, mtd->name, (unsigned long long)mtd->size);
}
static void ep_mtd_removed(struct mtd_info *mtd)
{
pr_info("ep_mtd_notify: removed mtd%d \"%s\"\n",
mtd->index, mtd->name);
}
static struct mtd_notifier ep_notifier = {
.add = ep_mtd_added,
.remove = ep_mtd_removed,
};
static int __init ep_mtd_notify_init(void)
{
register_mtd_user(&ep_notifier);
pr_info("ep_mtd_notify: loaded\n");
return 0;
}
static void __exit ep_mtd_notify_exit(void)
{
unregister_mtd_user(&ep_notifier);
pr_info("ep_mtd_notify: unloaded\n");
}
module_init(ep_mtd_notify_init);
module_exit(ep_mtd_notify_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala demo: log MTD add/remove events");
# Build against your running kernel headers, then:
sudo insmod ep_mtd_notify.ko
sudo modprobe mtdram total_size=1024 erase_size=16
sudo rmmod mtdram
sudo rmmod ep_mtd_notify
dmesg | tail
Expected dmesg output:
[ 102.441123] ep_mtd_notify: loaded
[ 102.558811] ep_mtd_notify: added mtd0 "mtdram test device" size=1048576
[ 108.902234] ep_mtd_notify: removed mtd0 "mtdram test device"
[ 109.014502] ep_mtd_notify: unloaded
Common Mistakes and Troubleshooting
- Treating an MTD block device like a normal disk.
/dev/mtdblockNexists for compatibility, but writing to it in place still triggers unsafe erase/write cycles unless the filesystem above it understands flash. Use raw/dev/mtdNwith flash-aware tools for anything but read-only filesystems. - Forgetting the two character nodes per partition. If a write to
/dev/mtdNis silently refused, check you didn’t open therosibling node by mistake. - Assuming one NAND driver fits all boards. Unlike NOR’s CFI standard, NAND controller drivers are not interchangeable across SoC families — you need the one matching your controller.
- Ignoring bad-block handling in custom tooling. Any code that reads/writes NAND directly must skip factory-marked bad blocks; the MTD core exposes calls for this, don’t bypass them.
Best Practices
- Always use
mtd-utils(flash_erase,nandwrite,nanddump,mtdinfo) instead of rawddagainst NAND in production — they handle bad blocks and OOB correctly. - Prefer the character device for anything that needs an explicit erase step; reserve the block device path for read-only filesystems.
- When bringing up a new board, verify the chip driver first with
mtdinfobefore layering a filesystem on top — it isolates driver bugs from filesystem bugs.
Performance Considerations
Erase operations are the slowest part of the MTD path by far, often tens of milliseconds per block on NAND. Batch writes to avoid repeated erase-write cycles on the same block, and remember that wear leveling — where applicable — happens above MTD, in filesystems like UBIFS, not inside the MTD core itself.
Security Considerations
Character MTD device nodes are typically root-only by default, and that’s intentional: raw erase access to a board’s flash can brick the bootloader partition. Keep partition permissions tight, and use the ro partition suffix (covered in the next lecture) to hardware-protect bootloader regions from accidental overwrite.
Summary and Key Takeaways
- MTD exists because raw flash can’t be treated like a disk: it erases in blocks and (for NAND) ships with bad blocks.
- The subsystem is a clean three-layer stack: chip drivers at the bottom, a uniform core in the middle, char/block device nodes on top.
- NOR drivers are few and standardized via CFI; NAND drivers are numerous and SoC-specific.
- You can prototype and learn the entire stack on a desktop machine using
mtdram, without any real flash hardware.
Conclusion
The MTD subsystem is the quiet foundation underneath nearly every embedded Linux board that boots from raw flash. Once you see it as three layers — hardware-specific chip drivers, a uniform core, and familiar device nodes on top — the rest of the storage stack (partitioning, JFFS2/UBIFS, bootloader flashing) becomes much easier to reason about. The next lecture in this free linux device drivers course builds directly on this foundation and covers how to actually carve an MTD chip into named partitions.
FAQ
What does MTD stand for in Linux?
MTD stands for Memory Technology Devices — the Linux kernel subsystem for managing raw NOR and NAND flash chips.
Is MTD the same as a block device?
No. MTD is a lower-level abstraction than the block layer; it exposes a block device node mainly for compatibility with filesystems that expect one, but writes still respect flash’s erase-before-write constraints.
Do I need real flash hardware to learn MTD?
No — the mtdram driver lets you create an MTD device backed by RAM, which is enough to practice the whole read/erase/write/inspect workflow shown in this lecture.
Why are there so many NAND drivers but few NOR drivers?
NOR flash is largely standardized around the CFI interface, so a handful of generic drivers cover most chips. NAND controllers are SoC-specific, so each board family needs its own driver.
What is the difference between /dev/mtd0 and /dev/mtd0ro?
They’re the same underlying MTD partition; /dev/mtd0 allows read-write access while /dev/mtd0ro is a read-only view of the identical data, used to prevent accidental writes.
Can I mount a filesystem directly on an MTD character device?
Flash-aware filesystems such as JFFS2 and UBIFS mount directly on the MTD character interface; ordinary filesystems expect the block device path instead.
Continue the Free Linux Kernel Development Course
Next up: carving an MTD chip into named partitions with device-tree, command-line, and runtime tooling.
Next Lecture Browse Full Course
2 Comments