linux flash mtd驱动分析

在嵌入式 Linux 系统中,若要直接通过 Flash 读写接口 (如 nvm_read、nvm_write 或 flash_read,flash_write)来操作 MTD 分区,通常需要借助 内核提供的接口 或 底层驱动 API。以下将结合 Flash 分区配置示例,说明如何使用这些接口实现读写操作。

首先查看评审的分区表,以256M内存分析:

分区名称 首地址 大小 用途说明
U-Boot 0x00000000 4MB 引导加载程序
env 0x00400000 256KB U-Boot 环境变量
Kernel 0x00440000 8MB Linux 内核镜像
RootFS 0x00C40000 100MB 根文件系统(如 UBIFS

二 使用 nvm_read 和 nvm_write 读写 Flash 分区

在嵌入式系统中,nvm_read 和 nvm_write 通常是由 底层驱动 或 Bootloader 提供的接口,用于直接访问 Flash 存储器。直接调用这些接口读写 Flash 分区。

1. 定义 Flash 分区信息

复制代码
#include <stdint.h>
#include <stdio.h>

// 定义 Flash 分区信息
typedef struct {
    const char *name;
    uint32_t start_addr;
    uint32_t size;
} flash_partition_t;

// 分区表
static const flash_partition_t flash_partitions[] = {
    {"u-boot", 0x00000000, 0x00400000},
    {"env",    0x00400000, 0x00040000},
    {"kernel", 0x00440000, 0x00800000},
    {"rootfs", 0x00C40000, 0x06400000},
    {"cfg",    0x07040000, 0x00100000},
    {"reserved", 0x07140000, 0x0ee00000},
};

#define NUM_PARTITIONS (sizeof(flash_partitions) / sizeof(flash_partition_t))

2. 声明 nvm_read 和 nvm_write 接口

假设底层驱动提供了以下接口:

复制代码
// 读取 Flash 数据
int nvm_read(uint32_t addr, uint8_t *buf, uint32_t len);

// 写入 Flash 数据(需先擦除)
int nvm_write(uint32_t addr, const uint8_t *buf, uint32_t len);

// 擦除 Flash 块
int nvm_erase(uint32_t addr, uint32_t len);

3. 读取指定分区的数据

复制代码
int read_partition(const char *name, uint8_t *buf, uint32_t *len) {
    for (int i = 0; i < NUM_PARTITIONS; i++) {
        if (strcmp(flash_partitions[i].name, name) == 0) {
            if (*len > flash_partitions[i].size) {
                printf("Error: Buffer too large for partition %s\n", name);
                return -1;
            }
            return nvm_read(flash_partitions[i].start_addr, buf, *len);
        }
    }
    printf("Error: Partition %s not found\n", name);
    return -1;
}

4. 写入数据到指定分区

复制代码
int write_partition(const char *name, const uint8_t *buf, uint32_t len) {
    for (int i = 0; i < NUM_PARTITIONS; i++) {
        if (strcmp(flash_partitions[i].name, name) == 0) {
            if (len > flash_partitions[i].size) {
                printf("Error: Data too large for partition %s\n", name);
                return -1;
            }
            // 先擦除分区
            if (nvm_erase(flash_partitions[i].start_addr, flash_partitions[i].size) != 0) {
                printf("Error: Failed to erase partition %s\n", name);
                return -1;
            }
            // 写入数据
            return nvm_write(flash_partitions[i].start_addr, buf, len);
        }
    }
    printf("Error: Partition %s not found\n", name);
    return -1;
}

5. 示例:读取 cfg 分区并打印内容

复制代码
int main() {
    uint8_t buf[1024];
    uint32_t len = sizeof(buf);

    // 读取 cfg 分区
    if (read_partition("cfg", buf, &len) == 0) {
        printf("Read %d bytes from cfg partition:\n", len);
        for (int i = 0; i < len; i++) {
            printf("%02x ", buf[i]);
            if ((i + 1) % 16 == 0) printf("\n");
        }
        printf("\n");
    } else {
        printf("Failed to read cfg partition\n");
    }

    return 0;
}

Kernel 设备树配置

在 Linux 设备树(.dts 文件)中,过 mtdparts 属性定义分区

复制代码
&nand {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        partition@0 {
            label = "u-boot";
            reg = <0x00000000 0x00400000>;
        };

        partition@400000 {
            label = "env";
            reg = <0x00400000 0x00040000>;
        };

        partition@440000 {
            label = "kernel";
            reg = <0x00440000 0x00800000>;
        };

        partition@c40000 {
            label = "rootfs";
            reg = <0x00C40000 0x06400000>;
        };

        partition@7040000 {
            label = "cfg";
            reg = <0x07040000 0x00100000>;
        };
    };
};

三、使用 MTD 接口实现 Flash 读写(C 代码示例)

1. 获取 MTD 分区信息

在 Linux 内核中,可以通过 mtd_get_partition_info() 获取分区的首地址和大小:

复制代码
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>

struct mtd_info *mtd;
struct mtd_partition partition;
int ret;

// 获取 MTD 设备(例如 mtd0)
mtd = get_mtd_device(NULL, 0);
if (IS_ERR(mtd)) {
    printk(KERN_ERR "Failed to get MTD device\n");
    return PTR_ERR(mtd);
}

// 获取分区信息(例如 "cfg" 分区)
ret = mtd_get_partition_info(mtd, "cfg", &partition);
if (ret) {
    printk(KERN_ERR "Failed to get partition info\n");
    put_mtd_device(mtd);
    return ret;
}

printk(KERN_INFO "Partition '%s': start=0x%llx, size=0x%llx\n",
       partition.name, partition.offset, partition.size);

2. 使用 mtd_read 和 mtd_write 读写 Flash

复制代码
#include <linux/mtd/mtd.h>
#include <linux/vmalloc.h>

#define BUF_SIZE 4096

struct mtd_info *mtd;
char *buf;
loff_t offset;
size_t retlen;
int ret;

// 获取 MTD 设备
mtd = get_mtd_device(NULL, 0);
if (IS_ERR(mtd)) {
    printk(KERN_ERR "Failed to get MTD device\n");
    return PTR_ERR(mtd);
}

// 分配缓冲区
buf = vmalloc(BUF_SIZE);
if (!buf) {
    printk(KERN_ERR "Failed to allocate buffer\n");
    put_mtd_device(mtd);
    return -ENOMEM;
}

// 读取 Flash 分区(例如 "cfg" 分区)
offset = 0x07040000; // cfg 分区首地址
ret = mtd_read(mtd, offset, BUF_SIZE, &retlen, buf);
if (ret) {
    printk(KERN_ERR "Failed to read Flash\n");
    vfree(buf);
    put_mtd_device(mtd);
    return ret;
}

printk(KERN_INFO "Read %zu bytes from Flash\n", retlen);

// 写入 Flash 分区(例如 "cfg" 分区)
ret = mtd_write(mtd, offset, BUF_SIZE, &retlen, buf);
if (ret) {
    printk(KERN_ERR "Failed to write Flash\n");
    vfree(buf);
    put_mtd_device(mtd);
    return ret;
}

printk(KERN_INFO "Wrote %zu bytes to Flash\n", retlen);

// 释放资源
vfree(buf);
put_mtd_device(mtd);

3. 擦除 Flash 分区(NAND Flash 必须先擦除)

复制代码
#include <linux/mtd/mtd.h>

struct mtd_info *mtd;
struct erase_info erase;
int ret;

// 获取 MTD 设备
mtd = get_mtd_device(NULL, 0);
if (IS_ERR(mtd)) {
    printk(KERN_ERR "Failed to get MTD device\n");
    return PTR_ERR(mtd);
}

// 擦除 Flash 分区(例如 "cfg" 分区)
erase.addr = 0x07040000; // cfg 分区首地址
erase.len = 0x00100000;  // cfg 分区大小
erase.mtd = mtd;

ret = mtd_erase(mtd, &erase);
if (ret) {
    printk(KERN_ERR "Failed to erase Flash\n");
    put_mtd_device(mtd);
    return ret;
}

printk(KERN_INFO "Erased Flash partition\n");
put_mtd_device(mtd);

四、 坏块识别原理

NAND Flash通常通过以下方式标记坏块:

  • 在OOB(Out-Of-Band)区域特定位置标记坏块
  • 不同厂商可能有不同的标记位置和值
  • 常见标记位置:第0页或第1页的OOB区域特定字节

完整代码实现

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <linux/mtd/mtd.h>
#include <sys/ioctl.h>

#define BLOCK_SIZE (128 * 1024)  // 假设块大小为128KB
#define PAGE_SIZE 2048          // 假设页大小为2KB
#define OOB_SIZE 64             // 假设OOB大小为64字节

// 坏块标记位置(不同Flash可能不同)
#define BAD_BLOCK_POS 0         // OOB区域第0字节

struct mtd_info_user mtd_info;

// 检查是否为坏块
int is_bad_block(int fd, unsigned int block_addr)
{
    unsigned char oob_data[OOB_SIZE];
    struct mtd_oob_buf oob;
    int ret;
    
    // 计算块内第一页的地址
    loff_t page_addr = block_addr * BLOCK_SIZE / PAGE_SIZE;
    
    // 读取OOB数据
    oob.start = page_addr * PAGE_SIZE;
    oob.length = OOB_SIZE;
    oob.ptr = oob_data;
    
    ret = ioctl(fd, MEMREADOOB, &oob);
    if (ret < 0) {
        perror("MEMREADOOB failed");
        return -1;
    }
    
    // 检查坏块标记
    if (oob_data[BAD_BLOCK_POS] != 0xFF) {
        printf("Block %u is marked as bad (OOB[0]=0x%02X)\n", 
               block_addr, oob_data[BAD_BLOCK_POS]);
        return 1;
    }
    
    return 0;
}

// 跳过坏块的读取接口
int read_skip_bad_blocks(int fd, void *buf, unsigned int start_block, 
                         unsigned int block_count)
{
    unsigned int current_block = start_block;
    unsigned int blocks_read = 0;
    unsigned int total_blocks = block_count;
    unsigned char *read_ptr = (unsigned char *)buf;
    
    while (blocks_read < total_blocks && current_block < mtd_info.size / BLOCK_SIZE) {
        // 检查当前块是否为坏块
        int bad = is_bad_block(fd, current_block);
        if (bad < 0) {
            return -1; // 检查失败
        }
        
        if (bad) {
            printf("Skipping bad block %u\n", current_block);
            current_block++;
            continue;
        }
        
        // 读取好块
        loff_t offset = current_block * BLOCK_SIZE;
        ssize_t bytes_read = pread(fd, read_ptr, BLOCK_SIZE, offset);
        
        if (bytes_read != BLOCK_SIZE) {
            perror("Read failed");
            return -1;
        }
        
        printf("Read block %u successfully\n", current_block);
        read_ptr += BLOCK_SIZE;
        blocks_read++;
        current_block++;
    }
    
    return blocks_read * BLOCK_SIZE;
}

// 跳过坏块的写入接口
int write_skip_bad_blocks(int fd, const void *buf, unsigned int start_block, 
                          unsigned int block_count)
{
    unsigned int current_block = start_block;
    unsigned int blocks_written = 0;
    unsigned int total_blocks = block_count;
    const unsigned char *write_ptr = (const unsigned char *)buf;
    
    while (blocks_written < total_blocks && current_block < mtd_info.size / BLOCK_SIZE) {
        // 检查当前块是否为坏块
        int bad = is_bad_block(fd, current_block);
        if (bad < 0) {
            return -1; // 检查失败
        }
        
        if (bad) {
            printf("Skipping bad block %u\n", current_block);
            current_block++;
            continue;
        }
        
        // 写入好块
        loff_t offset = current_block * BLOCK_SIZE;
        ssize_t bytes_written = pwrite(fd, write_ptr, BLOCK_SIZE, offset);
        
        if (bytes_written != BLOCK_SIZE) {
            perror("Write failed");
            return -1;
        }
        
        printf("Wrote block %u successfully\n", current_block);
        write_ptr += BLOCK_SIZE;
        blocks_written++;
        current_block++;
    }
    
    return blocks_written * BLOCK_SIZE;
}

int main(int argc, char *argv[])
{
    int fd;
    char *mtd_device = "/dev/mtd0"; // 默认MTD设备
    
    if (argc > 1) {
        mtd_device = argv[1];
    }
    
    // 打开MTD设备
    fd = open(mtd_device, O_RDWR);
    if (fd < 0) {
        perror("open mtd device failed");
        return 1;
    }
    
    // 获取MTD设备信息
    if (ioctl(fd, MEMGETINFO, &mtd_info) < 0) {
        perror("MEMGETINFO failed");
        close(fd);
        return 1;
    }
    
    printf("MTD device info:\n");
    printf("  Type: %u\n", mtd_info.type);
    printf("  Flags: %u\n", mtd_info.flags);
    printf("  Size: %u bytes (%u MB)\n", mtd_info.size, mtd_info.size / (1024*1024));
    printf("  Erasesize: %u bytes\n", mtd_info.erasesize);
    printf("  Writesize: %u bytes\n", mtd_info.writesize);
    printf("  OOB size: %u bytes\n", mtd_info.oobsize);
    
    // 更新块大小和页大小为实际值
    unsigned int block_size = mtd_info.erasesize;
    unsigned int page_size = mtd_info.writesize;
    unsigned int oob_size = mtd_info.oobsize;
    
    printf("\nScanning for bad blocks...\n");
    
    // 扫描所有块并打印坏块信息
    unsigned int total_blocks = mtd_info.size / block_size;
    unsigned int bad_blocks = 0;
    
    for (unsigned int i = 0; i < total_blocks; i++) {
        if (is_bad_block(fd, i) > 0) {
            bad_blocks++;
        }
    }
    
    printf("\nFound %u bad blocks out of %u total blocks\n", bad_blocks, total_blocks);
    
    // 测试读写跳过坏块
    if (total_blocks > 10) {
        unsigned char *read_buf = malloc(block_size * 5);
        unsigned char *write_buf = malloc(block_size * 5);
        
        if (!read_buf || !write_buf) {
            perror("malloc failed");
            free(read_buf);
            free(write_buf);
            close(fd);
            return 1;
        }
        
        // 准备测试数据
        memset(write_buf, 0xAA, block_size * 5);
        
        printf("\nTesting write with bad block skipping...\n");
        int written = write_skip_bad_blocks(fd, write_buf, 0, 5);
        if (written < 0) {
            printf("Write test failed\n");
        } else {
            printf("Successfully wrote %d bytes\n", written);
        }
        
        printf("\nTesting read with bad block skipping...\n");
        memset(read_buf, 0, block_size * 5);
        int read = read_skip_bad_blocks(fd, read_buf, 0, 5);
        if (read < 0) {
            printf("Read test failed\n");
        } else {
            printf("Successfully read %d bytes\n", read);
            
            // 验证数据
            if (memcmp(read_buf, write_buf, read) == 0) {
                printf("Data verification passed\n");
            } else {
                printf("Data verification failed\n");
            }
        }
        
        free(read_buf);
        free(write_buf);
    }
    
    close(fd);
    return 0;
}

3.

五、关键注意事项

1. 地址对齐

  • mtd_read 和 mtd_write 的地址和长度通常需要按 块(Block)或页(Page)对齐。
  • 例如,NAND Flash 的页大小可能是 2KB 或 4KB,写入时需对齐到页边界。

2. 擦写操作

  • NAND Flash 写入前必须先擦除对应块。
  • mtd_erase 通常按块擦除,需确保擦除范围覆盖写入区域。

3. 权限问题

  • 直接调用 mtd_read 和 mtd_write 通常需要 内核态权限 或 特权模式。
  • 在用户空间调用时,可能需要通过 内核模块 或 系统调用 实现。

4. 调试工具

  • 使用 dmesg | grep mtd 查看 MTD 设备的内核日志。
  • 使用 hexdump 或 dd 工具验证读写数据。
相关推荐
夜听莺儿鸣5 小时前
502-003_Linux 中断与异常(一):从Linux角度理解中断与异常
linux·中断与异常·linux中断
杨云龙UP6 小时前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
向成科技7 小时前
XC3576H工控主板|深度适配Ubuntu 26.04 LTS,释放边缘AI与工业开发新潜能
linux·人工智能·ubuntu·机器人·硬件·主板·边缘ai
Fcy6487 小时前
Linux下 进程间关系与守护进程
linux·运维·服务器·守护进程
码农小韩8 小时前
Linux驱动理论(二)——Linux字符设备驱动
linux·嵌入式软件开发·linux操作系统·linux应用开发·linux驱动理论
well06128 小时前
Linux粘滞位与Makefile机制深度解析
linux·运维·服务器
再写一行代码就下班8 小时前
linux sh脚本在windows修改导致无法使用解决方式
java·linux·centos
额额额对了8 小时前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
子木HAPPY阳VIP9 小时前
Ubuntu 关闭防火墙操作步骤
linux·运维·ubuntu
王振超wzc9 小时前
嵌入式开发环境搭建--WM软件安装,Ubuntu操作系统安装
linux·运维·ubuntu