linux flash mtd驱动分析

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

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

分区名称首地址大小用途说明
U-Boot0x000000004MB引导加载程序
env0x00400000256KBU-Boot 环境变量
Kernel0x004400008MBLinux 内核镜像
RootFS0x00C40000100MB根文件系统(如 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 工具验证读写数据。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌上花开缓缓归以

你的鼓励将是我创作的最大动力,

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值