MTD Character Devices Explained-Best Embedded Linux Training In Hyderabad

MTD Character Devices Explained
Raw flash access on embedded Linux, ioctl by ioctl

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.

MTD flash memory NAND NOR ioctl free embedded linux course

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.

OperationWhat It DoesTypical Use
Get infoReports flash type, size, erase block sizeSizing partitions correctly before writing
EraseErases one or more blocks in the MTD partitionRequired before any program (write) operation
Write OOBWrites out-of-band metadata for a NAND pageCustom ECC or bad-block marker handling
Read OOBReads out-of-band metadata for a NAND pageInspecting ECC status per page
Lock / UnlockLocks or unlocks a block against writes, where the chip supports itProtecting a bootloader partition
Get / Set bad blockReads or sets the bad-block flag on a NAND blockBad 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.

User Space Path to Raw Flash
application (your code / flash_erase / nandwrite) | | open(“/dev/mtdX”), read(), ioctl(MEMERASE, …) v MTD character device driver | | translates to chip-level erase/program/read v NOR or NAND flash controller –> physical flash chip

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.

AspectNOR FlashNAND Flash
Direct byte-copy writeWorks reliablyFails on first bad block
Bad blocksExtremely rareExpected, must be handled
Density / costLower density, more expensive per MBHigh density, cheaper per MB
Typical useSmall bootloaders, XIP codeRoot 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 cp on 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 erasesize will 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 MEMGETINFO first 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

Leave a Reply

Your email address will not be published. Required fields are marked *