If you’ve worked with hard disks or eMMC on Linux, you already know how block devices behave: you can seek anywhere, overwrite any sector, and the driver mostly gets out of your way. Raw NOR and NAND flash chips don’t play by those rules, and that’s exactly why the kernel gives you a completely different device type for them — the MTD character device. This lecture is part of our free embedded Linux course, and it’s the first stop in a short series on building a real storage strategy for flash-based embedded boards.
What You Will Learn
- Why raw flash memory needs its own device abstraction instead of reusing block devices
- What an MTD character device actually exposes to user space
- The core ioctl operations the kernel provides for erasing, locking, and inspecting flash
- How NOR and NAND flash differ when it comes to reading and writing from user space
- How to write a small original C utility that talks to an MTD character device directly
Prerequisites
- Comfortable reading and writing basic C, including
open(),read(),ioctl() - A working embedded Linux target (or QEMU) with an MTD device node such as
/dev/mtd0 - Familiarity with the idea of a kernel driver exposing a device node under
/dev
Why Flash Memory Cannot Behave Like a Disk
A traditional block device lets you overwrite any single sector in place. Flash memory physically cannot do that. You can only flip bits from 1 to 0 by writing (programming) a page, and the only way to flip bits back to 1 is to erase an entire block at once — usually tens or hundreds of kilobytes in one go, not a single 512-byte sector. On top of that, NAND flash chips ship from the factory with a small number of bad blocks, and more can develop over the life of the device.
Because of these physical quirks, the kernel doesn’t pretend flash is a disk. Instead it gives you the Memory Technology Device (MTD) subsystem, which exposes each flash partition as a character device — typically /dev/mtd0, /dev/mtd1, and so on — that lets you access the chip as a raw array of bytes, plus a set of flash-aware operations layered on top through ioctl().
What the MTD Character Device Exposes
Reading from an MTD character device works the way you’d expect: open the node, seek to an offset, and read bytes. Writing is where flash-awareness kicks in — you can only write to a region that has already been erased, and the driver enforces flash page/erase-block alignment rules underneath you.
Beyond plain read/write, the kernel defines a family of ioctl commands (found in the kernel’s MTD ABI header) that give user space control over operations a plain file descriptor can’t express — erasing a range, locking blocks against writes, and querying the out-of-band (OOB) area used for NAND error-correction metadata. The table below groups the ones you’ll actually reach for in day-to-day driver and application work.
| Operation | What It Does | Typical Use |
|---|---|---|
| Get info | Reports flash type, size, erase block size | Sizing partitions correctly before writing |
| Erase | Erases one or more blocks in the MTD partition | Required before any program (write) operation |
| Write OOB | Writes out-of-band metadata for a NAND page | Custom ECC or bad-block marker handling |
| Read OOB | Reads out-of-band metadata for a NAND page | Inspecting ECC status per page |
| Lock / Unlock | Locks or unlocks a block against writes, where the chip supports it | Protecting a bootloader partition |
| Get / Set bad block | Reads or sets the bad-block flag on a NAND block | Bad block scanning and remapping |
You rarely call these ioctls by hand in application code. Most of the time you reach for the small set of command-line tools built on top of them, which exist precisely so you don’t have to reinvent flash handling every time you need to erase or dump a partition.
NOR vs NAND: Writing Is Not the Same Operation
On NOR flash, once a region is erased, programming it is close to trivial — you can copy bytes straight onto the MTD device node and the hardware handles it cleanly, because NOR chips guarantee reliable, uniformly addressable storage.
NAND flash is a different story. A simple byte copy will break the moment it hits a bad block, because plain copy has no concept of skipping over damaged regions. That’s why NAND writing and reading go through purpose-built tools that walk the bad-block table for you, remapping around any block flagged bad rather than failing outright.
| Aspect | NOR Flash | NAND Flash |
|---|---|---|
| Direct byte-copy write | Works reliably | Fails on first bad block |
| Bad blocks | Extremely rare | Expected, must be handled |
| Density / cost | Lower density, more expensive per MB | High density, cheaper per MB |
| Typical use | Small bootloaders, XIP code | Root filesystems, bulk data storage |
Building a Small MTD Inspector
Let’s write a minimal, original utility called ep_mtdinfo that opens an MTD character device and reports its erase-block size and total size using the standard MTD info ioctl. This is intentionally small so you can see the exact syscall sequence without any library wrapping it for you.
// ep_mtdinfo.c — reports basic characteristics of an MTD partition
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <mtd/mtd-user.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: %s /dev/mtdX\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
struct mtd_info_user info;
if (ioctl(fd, MEMGETINFO, &info) < 0) {
perror("ioctl(MEMGETINFO)");
close(fd);
return 1;
}
printf("Device : %s\n", argv[1]);
printf("Type : %u\n", info.type);
printf("Total size : %u bytes\n", info.size);
printf("Erase size : %u bytes\n", info.erasesize);
printf("Write size : %u bytes\n", info.writesize);
close(fd);
return 0;
}
Build and run it on a target that has an MTD device node exposed:
$ gcc -o ep_mtdinfo ep_mtdinfo.c
$ sudo ./ep_mtdinfo /dev/mtd0
Device : /dev/mtd0
Type : 4
Total size : 8388608 bytes
Erase size : 65536 bytes
Write size : 2048 bytes
From here, erasing before writing follows the same pattern: fill an erase_info_user structure with the start offset and length, then issue the erase ioctl before touching that region with any write. In practice you’ll almost always reach for the flash_erase command-line tool rather than writing this by hand, but understanding the ioctl underneath makes debugging a stuck erase or a corrupted partition far less mysterious.
Real-World Use Cases
- Erasing and reflashing a bootloader or kernel partition during board bring-up
- Dumping a NAND partition for offline inspection after a corruption report from the field
- Writing a factory-provisioning tool that programs calibration data into a reserved MTD partition
- Building custom bad-block scanning tools for a NAND chip that isn’t well supported by stock utilities
Common Mistakes and Troubleshooting
- Writing without erasing first — flash can only flip bits one direction on write; skipping the erase step produces garbage data, not an error.
- Using plain
cpon NAND — this works by accident on a fresh NOR chip but silently corrupts data the moment a NAND write crosses a bad block. - Ignoring the erase-block size — attempting to erase a region that isn’t aligned to
erasesizewill be rejected by the driver. - Forgetting permissions — MTD device nodes are usually root-only by default; a permission-denied error is often just that.
Best Practices
- Always query
MEMGETINFOfirst so your tooling adapts to the actual erase/write size rather than hardcoding assumptions - Prefer the standard mtd-utils tools (
flash_erase,nandwrite,nanddump) over hand-rolled I/O for anything beyond diagnostics - Lock critical partitions like the bootloader where the hardware supports it, to guard against accidental erasure
Summary and Key Takeaways
- MTD character devices give raw, byte-addressable access to flash, but writes must always follow an erase
- The kernel exposes flash-specific operations — erase, lock/unlock, OOB read/write, bad-block query — through ioctls, not through plain read/write semantics
- NOR flash tolerates naive byte-copy writes; NAND flash absolutely does not, because of bad blocks
- A handful of ioctl calls are all it takes to build your own diagnostic tooling around MTD
Conclusion
MTD character devices are the foundation everything else in this storage series builds on — block emulation, filesystems, and translation layers all sit on top of this raw interface. Once you’re comfortable with how erase, program, and read map onto real ioctls, the higher-level pieces we’ll cover next start to make a lot more sense, because you’ll know exactly what’s happening underneath them.
FAQ
What is an MTD character device used for?
It gives user space raw, byte-level access to a flash memory partition, along with ioctl-based operations for erasing, locking, and querying the chip that a normal file device can’t express.
Can I just copy a file onto /dev/mtd0 like a normal file?
Only safely on NOR flash after erasing the target region first. On NAND flash, a plain copy will fail as soon as it encounters a bad block, so purpose-built tools are required.
Why do I need to erase flash before writing to it?
Flash memory can only change bits in one direction during a write. Reverting bits back requires erasing an entire block, which is a separate physical operation from programming.
What is the difference between MTD character and MTD block devices?
The character device gives raw, flash-aware access with explicit erase/program semantics. The block device (mtdblock) tries to present flash as an ordinary block device, with real limitations we’ll cover in the next lecture.
Do I need mtd-utils installed to work with MTD devices?
You don’t strictly need it to write your own ioctl-based tooling, but for everyday erase, write, and dump operations, mtd-utils saves you from re-implementing bad-block-aware logic yourself.
How do I find the erase block size of my flash chip?
Issue the MTD info ioctl (as shown in this lecture’s example) or run a tool that reports it, then use that size to align all erase and write operations.
Continue the Free Embedded Linux Course
Next up: how the kernel emulates a block device on top of flash, why it has real limits, and how to log kernel oops messages straight to an MTD partition.
Next Lecture Browse Full Course
2 Comments