In-Depth Analysis of Linux NOR Flash Drivers: From Hardware Principles to Kernel Implementation
1 Basic Concepts and Working Principles of NOR Flash
Flash memory, as a non-volatile storage medium, plays a critical role in embedded systems, server firmware, and various IoT devices. In the Linux ecosystem, NOR Flash, with its unique characteristics and access methods, has become an ideal storage medium for system boot firmware and critical data. Unlike common NAND Flash, NOR Flash has significant differences in physical structure, access methods, and application scenarios, and understanding these differences is fundamental to mastering Linux NOR Flash driver development.
1.1 Storage Principles and Physical Characteristics
Physically, each storage cell in NOR Flash is based on a three-terminal device, which includes a source, a drain, and a floating gate. The floating gate is surrounded by two layers of silicon dioxide insulation, forming a structure similar to a “charge trap”. When we inject charge into the floating gate, it indicates that binary data 0 has been written; while the absence of charge indicates binary 1. This is the opposite of traditional storage devices, so the erase operation for NOR Flash is actually a process of writing 1 to the entire block.
The erase and write processes of NOR Flash involve two different physical mechanisms: Hot Electron Injection and Fowler-Nordheim Tunneling. NOR Flash typically uses the hot electron injection method to complete write operations, which is fast but consumes more power; while the erase operation is achieved through the F-N tunneling effect, where charge is released from the floating gate through the insulation layer. This difference in physical mechanisms directly leads to the characteristic of NOR Flash having fast read speeds but slow erase and write speeds.
The charge retention capability in the floating gate determines the data retention capability of NOR Flash, which can typically last for over ten years. However, as the number of erase and write cycles increases, the insulation layer gradually wears out, leading to charge leakage, which is why NOR Flash also has a lifespan limitation. A typical NOR Flash block can withstand about 100,000 to 1,000,000 erase cycles, which, while far more than mechanical hard drives, still requires reasonable wear leveling algorithms to extend its lifespan.
1.2 NOR vs NAND: Core Differences Comparison
In practical applications, NOR Flash and NAND Flash have clear divisions of labor, and understanding their differences is crucial for correct technical selection.
| Characteristics | NOR Flash | NAND Flash |
|---|---|---|
| Read Performance | Fast random read speed, supports execute-in-place | Slow random read speed, fast sequential read speed |
| Write Performance | Slow write and erase speeds | Fast write and erase speeds |
| Storage Density | Lower, higher cost | High density, lower cost |
| Interface Method | Parallel address/data bus, similar to RAM | Complex I/O command interface |
| Reliability | Low bit error rate, usually no ECC required | High bit error rate, requires strong ECC verification |
| Main Uses | Firmware storage, boot code, critical data | Large capacity data storage, file systems |
From the table, it can be seen that the key advantages of NOR Flash lie in its fast random read capability and support for XIP (eXecute In Place) feature, which allows the CPU to directly fetch instructions from NOR Flash without first loading the code into RAM. This feature is particularly important for the system boot process and resource-constrained embedded environments.
The interface design of NOR Flash also reflects this: it has independent address and data buses, allowing direct access through addresses like traditional memory. For example, to read data at offset 0x1000 in NOR Flash, the CPU only needs to directly access the corresponding memory-mapped address. In contrast, NAND Flash requires a complex command sequence to specify the page and offset to access.
// Direct read method for NOR Flash
uint32_t data = *(uint32_t*)(NOR_BASE_ADDR + 0x1000);
// Complex read sequence for NAND Flash
nand_send_cmd(READ_CMD);
nand_send_addr(0x1000);
nand_send_cmd(READ_CMD_2);
nand_wait_ready();
uint32_t data = nand_read_data();
However, the disadvantages of NOR Flash are also evident: slow write and erase speeds, smaller capacity, and higher costs. These characteristics limit its application in large-capacity data storage, making it more suitable for storing boot code, operating system kernels, and critical configuration data that require high reliability and low update frequency.
2 Linux MTD Subsystem Architecture
To unify the management of various types of flash memory devices, the Linux kernel has designed the MTD (Memory Technology Device) subsystem. MTD serves as an intermediate layer, successfully abstracting the details of different underlying flash devices and providing a unified access interface for the upper layers. Whether it is NOR Flash, NAND Flash, or other flash technologies, they can all work in a consistent manner under the MTD framework, greatly simplifying the complexity of driver development and file system implementation.
2.1 MTD System Layered Structure
The MTD subsystem adopts a classic layered architecture, with each layer having clear responsibilities and communicating through well-defined interfaces. This design aligns with the design philosophy of the Linux kernel, enhancing code reusability and facilitating support for new hardware.
Hardware Layer
Kernel Space
User Space
Application
MTD Character Device Node
MTD Block Device Node
MTD Device Layer
MTD Raw Device Layer
NOR Flash Driver
NAND Flash Driver
Other Flash Drivers
NOR Hardware
NAND Hardware
Other Hardware
The layered structure of the MTD system is shown in the diagram above, clearly illustrating the flow of data from the application to the hardware device. Each layer focuses on specific functions and interacts with adjacent layers through standard interfaces.
2.2 Detailed Function Analysis of Each Layer
2.2.1 Device Node Layer
The device node layer serves as a bridge for interaction between user space and the kernel MTD subsystem. MTD creates two types of device nodes: character device nodes and block device nodes, which cater to different usage scenarios.
The character device node (major device number 90) provides raw access to the flash memory, allowing user space programs to directly perform read, write, erase, and IO control operations. In the character device’s minor device numbers, odd numbers represent read-only devices, while even numbers represent read-write devices. This design allows system administrators to control access permissions through device node permissions.
The block device node (major device number 31) allows the MTD device to be used as a block device, thus supporting the creation of traditional file systems on it, such as ext4, FAT, etc. The block device layer handles the complexity of erasing blocks and buffers write operations to improve performance.
In the system, the following commands can be used to view MTD device nodes:
# View MTD character device nodes
ls -l /dev/mtd*
# View MTD block device nodes
ls -l /dev/mtdblock*
# View MTD device information
cat /proc/mtd
2.2.2 MTD Device Layer
The MTD device layer is a key layer that connects the upper and lower layers, implementing the file operation interfaces for character and block devices and handling integration with specific file systems. For character devices, it implements standard file operations such as <span>open</span>, <span>close</span>, <span>read</span>, <span>write</span>, and <span>ioctl</span>; for block devices, it implements the <span>request</span> function to handle block read and write requests.
Importantly, the MTD device layer is responsible for hiding the erase block characteristics of the flash memory from the upper layers. For file systems, they see a continuous byte stream space without needing to care about the underlying erase block boundaries. This layer handles all complexities related to erase block management, including allocation, reclamation, and wear leveling of erase blocks.
2.2.3 MTD Raw Device Layer
The raw device layer is the core of the MTD subsystem, defining the basic interfaces and data structures for flash memory operations. The most important data structure is <span>mtd_info</span>, which contains a complete description of the flash device and a set of operation functions:
struct mtd_info {
uint32_t type; // Device type: MTD_NORFLASH, MTD_NANDFLASH, etc.
uint64_t size; // Total size of the device
uint32_t erasesize; // Erase block size
uint32_t writesize; // Write page size
// Core operation functions
int (*_read)(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*_write)(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_erase)(struct mtd_info *mtd, struct erase_info *instr);
// Synchronization operation functions
int (*_sync)(struct mtd_info *mtd);
// Other members and functions...
};
The raw device layer is also responsible for partition management, allowing a single physical flash device to be divided into multiple logical partitions. Each partition is treated as an independent MTD device, with its own <span>mtd_info</span> structure, and registered in the MTD device table. This partitioning mechanism allows the system to store different contents such as bootloaders, kernel images, and root file systems on the same flash device.
2.2.4 Hardware Driver Layer
The hardware driver layer is the lowest level implementation that interacts directly with the hardware. For NOR Flash, the main tasks of the driver are to initialize the chip, implement basic operations such as read and write erase, and register the device with the MTD subsystem.
NOR Flash drivers typically use the <span>map_info</span> structure to describe memory mapping information:
struct map_info {
const char *name; // Mapping name
unsigned long size; // Mapping size
unsigned long bankwidth; // Bus width
void __iomem *virt; // Virtual address
unsigned long phys; // Physical address
// Read and write functions
map_word (*read)(struct map_info *, unsigned long);
void (*write)(struct map_info *, const map_word, unsigned long);
// Other members...
};
Driver developers need to configure these parameters based on the specific hardware platform and NOR Flash chip, and then use the <span>do_map_probe</span> function to attempt different probing methods (such as <span>cfi_probe</span> and <span>jedec_probe</span>) to identify and initialize the Flash chip.
3 Core Data Structures and Relationships
To deeply understand the Linux NOR Flash driver, it is essential to grasp its core data structures and their interrelationships. These data structures form the backbone of the MTD subsystem, defining the abstract model and behavioral specifications of flash memory devices. By analyzing these data structures, we can gain a clearer understanding of the design philosophy and implementation mechanisms of the entire driver framework.
3.1 Core Data Structure Definitions
3.1.1 mtd_info Structure
<span>mtd_info</span> is the most important data structure in the MTD subsystem, describing all characteristics and capabilities of an MTD device. Whether it is NOR Flash, NAND Flash, or other storage technologies, they ultimately provide services to the upper layers through this unified interface.
struct mtd_info {
u_char type; // Device type
uint32_t flags; // Device flags
uint64_t size; // Total capacity of the device
uint32_t erasesize; // Erase block size
uint32_t writesize; // Minimum write size
uint32_t writebufsize; // Write buffer size
uint32_t oobsize; // Out-of-band data area size
uint32_t oobavail; // Available out-of-band data bytes
// Read operation
int (*_read)(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
// Write operation
int (*_write)(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
// Erase operation
int (*_erase)(struct mtd_info *mtd, struct erase_info *instr);
// Synchronization operation
int (*_sync)(struct mtd_info *mtd);
// Lock mechanism
int (*_lock)(struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_unlock)(struct mtd_info *mtd, loff_t ofs, uint64_t len);
// Bad block management
int (*_block_isbad)(struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad)(struct mtd_info *mtd, loff_t ofs);
// Power management
int (*_suspend)(struct mtd_info *mtd);
void (*_resume)(struct mtd_info *mtd);
// Reference counting
struct module *owner;
int usecount;
};
Each <span>mtd_info</span> structure represents an MTD device, which can be either an entire physical device or a partition. This abstraction allows the upper file systems to handle different underlying hardware consistently without needing to care about specific storage technology details.
3.1.2 map_info Structure
<span>map_info</span> structure is specifically used to describe the memory mapping information of NOR Flash. Since NOR Flash supports direct access similar to memory, it needs to be mapped into the processor’s address space through memory mapping.
struct map_info {
const char *name;
unsigned long size; // Size of the mapped area
unsigned long phys; // Physical address
void __iomem *virt; // Virtual address
unsigned long bankwidth; // Bus width: 1, 2, 4 bytes
// Chip operation functions
map_word (*read)(struct map_info *, unsigned long);
void (*write)(struct map_info *, const map_word, unsigned long);
void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);
void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);
// Asynchronous operation functions
void (*pfow_base)(unsigned long pfow_base);
// Cache control
void (*inval_cache)(struct map_info *, unsigned long, ssize_t);
// Chip settings
int (*set_vpp)(struct map_info *, int);
// Cache control
void *cached;
void *cache;
unsigned long cache_word;
int cache_page;
};
<span>map_info</span> structure’s <span>virt</span> member is particularly important as it holds the starting address of the NOR Flash mapped to the kernel’s virtual address space. Through this pointer, the driver can read the contents of NOR Flash directly as if accessing memory. However, write operations usually require more complex command sequences and cannot be completed through direct memory writes.
3.1.3 mtd_partition Structure
<span>mtd_partition</span> structure is used to describe the partition information of an MTD device, allowing a single physical MTD device to be divided into multiple logical partitions, each of which can be used as an independent MTD device.
struct mtd_partition {
const char *name; // Partition name
uint64_t size; // Partition size
uint64_t offset; // Offset in the physical device
uint32_t mask_flags; // Mask flags (e.g., read-only)
struct mtd_info **mtdp; // Pointer to pointer of MTD device pointers
};
In actual driver development, developers need to define the partition table based on hardware design, for example:
static struct mtd_partition s3c_nor_parts[] = {
[0] = {
.name = "bootloader_nor",
.size = 0x00040000, // 256KB
.offset = 0, // From the start of the device
},
[1] = {
.name = "root_nor",
.offset = MTDPART_OFS_APPEND, // Immediately after the previous partition
.size = MTDPART_SIZ_FULL, // Use all remaining space
}
};
This partitioning mechanism allows the system to store different contents such as bootloaders, kernel images, and root file systems on the same physical flash, with each partition adopting the most suitable file system format.
3.2 Data Structure Relationship Diagram
The core data structures in the NOR Flash driver are interconnected through carefully designed pointers and callback functions, forming an organic whole. Understanding the relationships between these data structures is crucial for mastering the working principles of the driver.
Created by do_map_probe
Partitions point to the main device
Contains private data
map_info
+char* name
+unsigned long size
+unsigned long phys
+void __iomem* virt
+map_word read()
+void write()
mtd_info
+u_char type
+uint64_t size
+uint32_t erasesize
+int _read()
+int _write()
+int _erase()
mtd_partition
+char* name
+uint64_t size
+uint64_t offset
cfi_private
+uint16_t manufacturer_id
+uint16_t device_id
+struct cfi_ident cfid
The diagram above illustrates the relationships between the core data structures in the NOR Flash driver. The <span>map_info</span> describes memory mapping information, creating the corresponding <span>mtd_info</span> through the <span>do_map_probe</span> function. The <span>mtd_partition</span> structure references the main <span>mtd_info</span> device, representing one of its logical partitions.
4 NOR Flash Driver Example Analysis
Combining theory with practice is the best way to deeply understand Linux kernel drivers. In this section, we will analyze a specific NOR Flash driver example in detail, from device initialization to the implementation of operation functions, and provide a simple usable driver code framework.
4.1 Driver Initialization Process
The initialization of the NOR Flash driver mainly includes three key steps: establishing memory mapping, device probing and identification, and MTD device registration. Below, we will illustrate this using the NOR Flash driver for the S3C series processors.
static struct mtd_info *s3c_nor_mtd;
static struct map_info *s3c_nor_map;
static int __init s3c_nor_init(void)
{
int ret = 0;
/* 1. Allocate map_info structure */
s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);
if (!s3c_nor_map) {
printk(KERN_ERR "Failed to allocate map_info\n");
return -ENOMEM;
}
/* 2. Set memory mapping parameters */
s3c_nor_map->name = "s3c_nor";
s3c_nor_map->phys = 0; // Physical address, modify according to actual hardware
s3c_nor_map->size = 0x1000000; // 16MB, adjust according to actual Flash size
s3c_nor_map->bankwidth = 2; // 16-bit width
/* 3. Establish memory mapping */
s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);
if (!s3c_nor_map->virt) {
printk(KERN_ERR "Failed to ioremap NOR Flash\n");
kfree(s3c_nor_map);
return -EIO;
}
/* 4. Simple initialization */
simple_map_init(s3c_nor_map);
/* 5. Attempt to probe the device, supporting both CFI and JEDEC standards */
printk("Use cfi_probe\n");
s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);
if (!s3c_nor_mtd) {
printk("Use jedec_probe\n");
s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);
}
if (!s3c_nor_mtd) {
printk(KERN_ERR "Failed to probe NOR Flash\n");
iounmap(s3c_nor_map->virt);
kfree(s3c_nor_map);
return -ENXIO;
}
/* 6. Set MTD device owner */
s3c_nor_mtd->owner = THIS_MODULE;
/* 7. Add MTD partitions */
ret = add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, ARRAY_SIZE(s3c_nor_parts));
if (ret) {
printk(KERN_ERR "Failed to add partitions\n");
map_destroy(s3c_nor_mtd);
iounmap(s3c_nor_map->virt);
kfree(s3c_nor_map);
return ret;
}
printk(KERN_INFO "S3C NOR Flash driver initialized successfully\n");
return 0;
}
The driver initialization process can be likened to the process of opening a store: first, you need to rent a storefront (allocate <span>map_info</span>), then determine the location and size of the storefront (set mapping parameters), then get the keys to the storefront (<span>ioremap</span> to establish mapping), arrange the storefront (<span>simple_map_init</span>), determine the types of goods to be sold (<span>do_map_probe</span> to probe the device), and finally open for business (<span>add_mtd_partitions</span> to register the device).
4.2 Partition Table Definition
The partition table defines how to divide the physical NOR Flash device into multiple logical areas, each used for different purposes. In actual projects, partition design needs to comprehensively consider boot processes, system upgrades, and data management.
static struct mtd_partition s3c_nor_parts[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000, // 256KB
.offset = 0, // Starting address 0
.mask_flags = MTD_WRITEABLE, // Read-only, protect the boot program
},
[1] = {
.name = "kernel",
.size = 0x00200000, // 2MB
.offset = MTDPART_OFS_APPEND,// Immediately after the previous partition
},
[2] = {
.name = "rootfs",
.size = 0x00D00000, // 13MB
.offset = MTDPART_OFS_APPEND,
},
[3] = {
.name = "userdata",
.size = MTDPART_SIZ_FULL, // Use all remaining space
.offset = MTDPART_OFS_APPEND,
}
};
In this partition design, we created four logical partitions:
- • bootloader: Stores the system boot program, set to read-only to prevent accidental modification
- • kernel: Stores the operating system kernel image
- • rootfs: Stores the root file system
- • userdata: Stores application data and configurations
This partition design conforms to the typical layout of embedded systems, ensuring the security of critical system components while providing sufficient storage space for applications.
4.3 Implementation of Device Operation Functions
The NOR Flash driver needs to implement a series of device operation functions, including read, write, and erase. These functions provide services to the upper layers through the <span>mtd_info</span> structure.
4.3.1 Read Function Implementation
The read operation of NOR Flash is relatively simple, as it supports random access and can directly read data through memory mapping.
static int s3c_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
struct map_info *map = mtd->priv;
struct cfi_private *cfi = map->fldrv_priv;
/* Check read range */
if (from + len > mtd->size) {
printk(KERN_ERR "Read beyond flash size: from=0x%llx, len=%zu\n",
from, len);
return -EINVAL;
}
/* Direct memory copy */
memcpy_fromio(buf, map->virt + from, len);
*retlen = len;
return 0;
}
The implementation of the read function fully utilizes the XIP feature of NOR Flash, directly reading data from the mapped virtual address using the <span>memcpy_fromio</span> function. This implementation method is simple and efficient, but attention must be paid to address boundary checks to prevent reading beyond the device range.
4.3.2 Erase Function Implementation
Erasing is the most complex part of NOR Flash operations, as NOR Flash requires confirming the status of the erase block before performing the erase, followed by sending a specific command sequence.
static int s3c_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
{
struct map_info *map = mtd->priv;
unsigned long addr, len;
int ret;
addr = instr->addr;
len = instr->len;
/* Validate erase parameters */
if (addr + len > mtd->size) {
printk(KERN_ERR "Erase beyond flash size\n");
return -EINVAL;
}
if (addr % mtd->erasesize || len % mtd->erasesize) {
printk(KERN_ERR "Erase not aligned to erase block\n");
return -EINVAL;
}
/* Perform erase operation */
ret = do_erase_oneblock(map, addr, len);
if (ret) {
printk(KERN_ERR "Erase failed at 0x%lx\n", addr);
instr->state = MTD_ERASE_FAILED;
} else {
instr->state = MTD_ERASE_DONE;
}
/* Call completion callback */
mtd_erase_callback(instr);
return ret;
}
The erase operation must be performed according to the erase block boundaries, which is an important limitation of NOR Flash. The driver needs to ensure that both the erase address and length are multiples of the erase block size. The actual erase operation is completed by writing predefined command sequences to specific Flash addresses, a process implemented by the <span>do_erase_oneblock</span> function.
4.3.3 Write Function Implementation
The write operation of NOR Flash also needs to be completed through a command sequence, and since NOR Flash bits can only change from 1 to 0 and not from 0 to 1, it is usually necessary to ensure that the target area has been erased (all 1s) before writing.
static int s3c_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct map_info *map = mtd->priv;
size_t written = 0;
int ret;
/* Check write range */
if (to + len > mtd->size) {
printk(KERN_ERR "Write beyond flash size\n");
return -EINVAL;
}
/* Write in chunks */
while (written < len) {
ret = do_write_oneword(map, to + written, buf + written);
if (ret) {
printk(KERN_ERR "Write failed at 0x%llx\n", to + written);
return ret;
}
written += 2; // 16-bit width, writing 2 bytes at a time
(*retlen) += 2;
}
return 0;
}
The write operation is typically performed in words or bytes, depending on the bit width of the NOR Flash. During the write process, the driver needs to handle possible write failures and return appropriate error codes to the upper layers in case of errors.
4.4 Detailed Explanation of the Driver Probing Process
The probing process of the NOR Flash driver is a key step in identifying and initializing the Flash chip. The Linux kernel supports various probing methods, mainly including CFI (Common Flash Interface) and JEDEC (Joint Electron Device Engineering Council) standards.
Flash chip jedec_probecfi_probedo_map_probe upper driver Flash chip jedec_probecfi_probedo_map_probe upper driver alt[JEDEC identification successful][JEDEC identification failed]alt[CFI identification successful][CFI identification failed]call probing function to attempt CFI probing send CFI query command return CFI information return mtd_info return NULL attempt JEDEC probing send JEDEC query command return manufacturer and device ID return mtd_info return NULL return probing result
CFI is a modern standard interface widely supported by NOR Flash chips, allowing drivers to query the Flash’s capacity, timing parameters, and operation command set through standard commands. The JEDEC standard identifies chips by reading the manufacturer ID and device ID, suitable for older or non-CFI supporting Flash chips.
5 Core Mechanisms and Framework Analysis
The support for NOR Flash in Linux goes beyond basic read, write, and erase operations, involving a series of complex mechanisms and frameworks that collectively ensure the efficient and reliable use of flash memory devices. A deep understanding of these core mechanisms is essential for developing high-quality flash drivers and optimizing system performance.
5.1 Address Mapping Mechanism
Address mapping is the fundamental mechanism of the NOR Flash driver, solving the problem of converting physical flash addresses to the processor’s virtual address space. Since NOR Flash supports XIP (execute-in-place), its address mapping mechanism has unique characteristics and importance compared to other storage technologies.
The address mapping of NOR Flash can be understood as a kind of window mechanism, through which the CPU can directly access Flash storage units. This process involves multiple address space conversions:
- 1. Physical Flash Address: The actual address inside the Flash chip
- 2. System Physical Address: The physical address allocated to Flash in the processor’s address space
- 3. Kernel Virtual Address: The address that the kernel can directly access after mapping through the page table
// Address mapping example
static int setup_memory_mapping(struct map_info *map)
{
// Allocate kernel virtual address space
map->virt = ioremap(map->phys, map->size);
if (!map->virt) {
printk(KERN_ERR "ioremap failed for NOR Flash at 0x%lx\n",
map->phys);
return -ENOMEM;
}
printk(KERN_INFO "NOR Flash mapped: phys=0x%lx, virt=0x%p, size=0x%lx\n",
map->phys, map->virt, map->size);
return 0;
}
Establishing address mapping is usually completed during the driver initialization phase, requiring the reservation of the corresponding address area in the system memory mapping table. Different processor architectures have different memory mapping configurations, which means that NOR Flash drivers often contain a certain amount of platform-specific code.
5.2 File System Interface
The MTD subsystem provides file system interfaces to user space through device nodes, allowing applications to access NOR Flash devices as if they were ordinary files. MTD provides two types of device nodes: character devices and block devices, catering to different usage scenarios.
5.2.1 MTD Character Device
The MTD character device (major device number 90) provides a raw access interface to the flash memory, allowing direct read, write, erase, and IO control operations. The character device interface is simple and direct, suitable for scenarios requiring fine control over flash operations, such as system upgrades and firmware updates.
The main operation functions of the character device are as follows:
static const struct file_operations mtd_fops = {
.owner = THIS_MODULE,
.llseek = mtd_lseek, // Seek
.read = mtd_read, // Read
.write = mtd_write, // Write
.unlocked_ioctl = mtd_ioctl, // Control commands
.open = mtd_open, // Open device
.release = mtd_close, // Close device
.mmap = mtd_mmap, // Memory mapping
};
Through ioctl commands, user space programs can obtain flash information, perform erase operations, check for bad blocks, etc. For example, the basic process for updating firmware is as follows:
# 1. Query Flash information
flash_info /dev/mtd1
# 2. Erase target partition
flash_erase /dev/mtd1 0 0
# 3. Write new firmware
nandwrite -p /dev/mtd1 new_firmware.bin
# 4. Verify write
nandread -o /dev/mtd1 read_back.bin
md5sum new_firmware.bin read_back.bin
5.2.2 MTD Block Device
The MTD block device (major device number 31) simulates the flash memory as a traditional block device, supporting the creation of common file systems such as ext4, FAT, etc. The block device interface hides the erase block characteristics of the flash memory, allowing existing file systems to run directly on flash devices without modification.
The core of the block device is the <span>mtd_blktrans_ops</span> structure:
static struct mtd_blktrans_ops mtdblock_tr = {
.name = "mtdblock",
.major = 31, // Major device number
.part_bits = 0,
.blksize = 512, // Simulate 512-byte sectors
.open = mtdblock_open, // Open
.flush = mtdblock_flush, // Flush
.release = mtdblock_release, // Release
.readsect = mtdblock_readsect, // Read sector
.writesect = mtdblock_writesect, // Write sector
.add_mtd = mtdblock_add_mtd, // Add MTD device
.remove_dev = mtdblock_remove_dev, // Remove device
.owner = THIS_MODULE,
};
The block device layer is responsible for converting file system sector requests (typically 512 bytes) into page operations for the underlying flash memory, handling complexities such as erase block alignment and write buffering. Although this simulation incurs some performance overhead, it greatly enhances system compatibility and usability.
5.3 Data Flow Overview Analysis
Understanding the data flow of NOR Flash operations is crucial for analyzing and optimizing system performance. From the application issuing read and write requests to the data finally reaching the Flash chip, it undergoes multiple layers of processing and transformation.
Hardware Layer NOR Driver Layer MTD Raw Device Layer MTD Device Layer File System VFS Layer Application Hardware Layer NOR Driver Layer MTD Raw Device Layer MTD Device Layer File System VFS Layer Application MTD Device Layer handles erase block alignment Driver handles command sequences and timing control read/write system calls file operations mtd_read/mtd_write_read/_write chip-specific operations send commands and data return status and data operation results return status operation complete return data system calls return
This data flow diagram illustrates the complete path from user space to the hardware layer. Each layer has specific responsibilities:
- • VFS Layer: Provides a unified file operation interface
- • File System Layer: Manages file and directory structures, handling metadata
- • MTD Device Layer: Handles erase block alignment and buffer management
- • MTD Raw Device Layer: Provides chip-independent flash operations
- • Driver Layer: Implements chip-specific command sequences and timing control
In practical operations, the write path is much more complex than the read path. Write operations need to handle erase prerequisites— ensuring that the target area has been erased before writing. This often requires the driver to maintain a write buffer or use write merging techniques to optimize the performance of small writes.
6 Tool Commands and Debugging Methods
Developing and debugging NOR Flash drivers requires support from a range of tools and methods. Mastering these tools and debugging techniques can significantly improve development efficiency and quickly locate and resolve issues. This section will introduce commonly used tool commands and kernel debugging techniques for the MTD subsystem.
6.1 MTD Toolset
MTD-utils is a user space toolset specifically designed for managing MTD devices, providing a series of practical tools for erasing, reading, writing, and checking flash memory devices. These tools are essential for developing and debugging NOR Flash drivers.
| Tool Command | Main Function | Usage Example | Description |
|---|---|---|---|
<span>flash_info</span> |
Query Flash information | <span>flash_info /dev/mtd0</span> |
Displays basic information about the MTD device |
<span>flash_erase</span> |
Erase Flash blocks | <span>flash_erase /dev/mtd0 0 0</span> |
Erases the entire MTD device |
<span>nandwrite</span> |
Write data | <span>nandwrite -p /dev/mtd0 file.bin</span> |
Writes a file to the MTD device |
<span>nandread</span> |
Read data | <span>nandread -o /dev/mtd0 file.bin</span> |
Reads data from the MTD device |
<span>mtd_debug</span> |
Debug access | <span>mtd_debug info /dev/mtd0</span> |
Displays detailed MTD information |
<span>ubinfo</span> |
UBI information | <span>ubinfo -a</span> |
Displays UBI volume information |
Using these tools can accomplish daily management and maintenance tasks for NOR Flash. For example, the basic process for updating firmware is as follows:
# 1. Query Flash information
flash_info /dev/mtd1
# 2. Erase target partition
flash_erase /dev/mtd1 0 0
# 3. Write new firmware
nandwrite -p /dev/mtd1 new_firmware.bin
# 4. Verify write
nandread -o /dev/mtd1 read_back.bin
md5sum new_firmware.bin read_back.bin
6.2 Kernel Debugging Techniques
In addition to user space tools, kernel space debugging methods are equally important for developing NOR Flash drivers. Through the debugging interfaces provided by the kernel, we can gain insights into the internal state and behavior of the driver.
6.2.1 Dynamic Debug Output
The dynamic debugging mechanism of the Linux kernel allows control over the output of debugging information at runtime, which is very useful for debugging NOR Flash drivers.
// Add dynamic debugging in the driver
#define dev_dbg(dev, fmt, ...) \
dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
// Add debugging information in the initialization function
dev_dbg(&pdev->dev, "Probing NOR Flash at phys=0x%lx, size=0x%lx\n",
map->phys, map->size);
// Add debugging information in operation functions
dev_dbg(&pdev->dev, "Read from 0x%llx, len %zu\n", from, len);
Enable dynamic debugging:
# Enable debugging information for a specific driver
echo -n 'file nor_flash.c +p' > /sys/kernel/debug/dynamic_debug/control
# Enable debugging information for the entire subsystem
echo -n 'file mtd* +p' > /sys/kernel/debug/dynamic_debug/control
6.2.2 Proc and Sysfs Interfaces
The MTD subsystem exposes device information through <span>/proc/mtd</span> and sysfs interfaces, which are important ways to obtain the status of the system’s MTD devices.
# View MTD device information
cat /proc/mtd
# Output example
dev: size erasesize name
mtd0: 00040000 00020000 "bootloader"
mtd1: 00200000 00020000 "kernel"
mtd2: 00d00000 00020000 "rootfs"
mtd3: 02100000 00020000 "userdata"
# View detailed attributes through sysfs
ls /sys/class/mtd/mtd0/
cat /sys/class/mtd/mtd0/name
cat /sys/class/mtd/mtd0/size
cat /sys/class/mtd/mtd0/erasesize
6.2.3 Hardware-Specific Debugging
For NOR Flash driver development, sometimes it is necessary to interact directly with the hardware for debugging. This can be achieved through device-specific commands or direct access to hardware registers.
// Read the manufacturer ID and device ID of NOR Flash
static void read_flash_ids(struct map_info *map)
{
map_word manufacturer_id, device_id;
// Send query manufacturer ID command
write_map_word(map, CMD(0x90), map->virt + 0x555);
manufacturer_id = read_map_word(map, map->virt);
// Send query device ID command
write_map_word(map, CMD(0x90), map->virt + 0x556);
device_id = read_map_word(map, map->virt + 1);
printk(KERN_INFO "NOR Flash ID: Manufacturer=0x%04x, Device=0x%04x\n",
manufacturer_id.x[0], device_id.x[0]);
}
This low-level hardware debugging is very useful for verifying hardware connections and identifying Flash chip models, especially when porting drivers to new hardware platforms.
7 Conclusion
Through an in-depth analysis of the Linux NOR Flash driver, we can clearly see the design philosophy and implementation path of modern operating systems managing complex hardware devices. From the physical characteristics of hardware to the layered architecture of kernel subsystems, from the careful design of core data structures to the comprehensive support of toolsets, every aspect reflects the essence of software engineering.
The support for NOR Flash in Linux is a complete ecosystem that encompasses multiple technical levels:
- • Hardware Level: Understanding the physical characteristics, interface methods, and access protocols of NOR Flash is the foundation of driver development
- • Driver Level: The layered architecture and unified interface of the MTD subsystem simplify driver development and enhance code reuse
- • System Level: Subsystems such as file system interfaces, memory management, and process scheduling closely collaborate with MTD
- • Tool Level: A complete set of user space tools provides strong support for device management and debugging
This complete technical system ensures that NOR Flash can work efficiently and reliably in Linux systems, meeting the application needs from embedded devices to server systems.