在嵌入式 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
工具验证读写数据。