nand flash bbt和bmt管理

驱动支持两种坏块管理模式:

  • REMAP BBTCONFIG_RTK_REMAP_BBT):虚拟块到物理块的映射 + 备用块重映射。

  • NORMAL BBTCONFIG_RTK_NORMAL_BBT):仅记录坏块位置,不进行重映射。

1,常用结构体

复制代码
// REMAP BBT 所需结构
typedef struct __attribute__ ((__packed__)) {
    u16 BB_die;          // 坏块所在芯片(多芯片支持,本驱动仅单芯片)
    u16 bad_block;       // 坏块在虚拟地址空间中的块号(初始为 BB_INIT=0xFFFE)
    u16 RB_die;          // 备用块所在芯片
    u16 remap_block;     // 备用块的实际物理块号(初始为 RB_INIT=0xFFFD)
} BB_t;

typedef struct __attribute__ ((__packed__)) {
    u32 block_r;         // 虚拟块映射到的实际物理块号
} BB_v2r;

// NORMAL BBT 所需结构
typedef struct __attribute__ ((__packed__)) {
    u16 BB_die;
    u16 bad_block;       // 坏块物理块号
    u16 RB_die;
    u16 block_info;      // 保留信息
} BBT_normal;

struct rtknflash {
    ...
#ifdef CONFIG_RTK_REMAP_BBT
    BB_t     *bbt;
    BB_v2r   *bbt_v2r;
    unsigned int RBA;           // 备用块数量
    unsigned int RBA_PERCENT;   // 默认 5%
#endif
#ifdef CONFIG_RTK_NORMAL_BBT
    unsigned int bbt_num;
    unsigned int BBT_PERCENT;   // 默认 5%
    BBT_normal *bbt_nor;
#endif
    unsigned int BBs;           // 实际坏块个数
    ...
};

坏块表存储位置

复制代码
#define REMAP_BBT_POS          0x100000  /* 1MB位置,重映射BBT */
#define NORMAL_BBT_POSITION    0x200000  /* 2MB位置,普通BBT */
#define BACKUP_BBT 3                     /* 备份BBT数量 */
#define BOOT_SIZE              0x100000  /* 启动区域大小1MB */

3. 坏块管理流程分析

复制代码
int rtkn_scan_bbt(struct mtd_info *mtd) {
    // 获取 chip 和 rtkn 指针
#ifdef CONFIG_RTK_REMAP_BBT
    // 计算备用块数量 RBA = 总块数 * 5%
    block_v2r_num = this->chipsize >> this->phys_erase_shift;
    RBA = rtkn->RBA = block_v2r_num * rtkn->RBA_PERCENT / 100;
    // 分配 BBT 和 v2r 表内存
    rtkn->bbt = kmalloc(sizeof(BB_t) * RBA, GFP_KERNEL);
    rtkn->bbt_v2r = kmalloc(sizeof(BB_v2r) * block_v2r_num, GFP_KERNEL);
    // 扫描或创建 v2r 映射表和坏块表
    if (rtk_scan_v2r_bbt(mtd) < 0 || rtk_nand_scan_bbt(mtd) < 0)
        goto Error;
#endif
#ifdef CONFIG_RTK_NORMAL_BBT
    // 计算 BBT 条数 = 总块数 * 5%
    bbt_num = rtkn->bbt_num = (mtd->size / mtd->erasesize) * rtkn->BBT_PERCENT / 100;
    rtkn->bbt_nor = kmalloc(sizeof(BBT_normal) * bbt_num, GFP_KERNEL);
    if (nand_scan_normal_bbt(mtd) < 0) goto Error;
#endif
}
  • RBA_PERCENTBBT_PERCENT 默认为 5,即保留 5% 的物理块作为备用块或 BBT 存储空间。

  • 备用块数量 固定为总物理块的 5%,不可动态调整。

  • 虚拟块数量 = 总物理块 - RBA(跳过保留区域)。

    static int create_v2r_remapping(struct mtd_info *mtd, unsigned int page,
    unsigned int block_v2r_num) {
    // 目的:为每一个虚拟块分配一个好的物理块
    unsigned int offs_real = 0, offs = 0;
    int count = 0;
    while (offs_real < (block_v2r_num << this->phys_erase_shift)) {
    if (rtk_block_isbad(mtd, numchips-1, offs_real)) {
    offs_real += block_size; // 跳过坏块
    } else {
    rtkn->bbt_v2r[count].block_r = offs_real >> this->phys_erase_shift;
    offs += block_size;
    offs_real += block_size;
    count++;
    }
    }
    // 将 v2r 表写入 NAND 的指定区域(bbt_v2r_page)
    rtk_scan_write_bbt(...);
    }

    static int scan_last_die_BB(struct mtd_info *mtd) {
    // 在芯片末尾的 RBA 个块中找出所有好块,作为备用块池
    for (addr = chip_size - block_size; addr >= 0; addr -= block_size) {
    if (rtk_block_isbad(mtd, numchips-1, addr))
    continue;
    remap_block[remap_count++] = (addr >> this->phys_erase_shift);
    if (remap_count == RBA) break;
    }
    // 将找到的备用块填入 bbt[i].remap_block(初始时所有 bad_block = BB_INIT)
    for (i = 0; i < RBA; i++)
    rtkn->bbt[i].remap_block = remap_block[i];
    }

    int rtk_remapBBT_write_fail(struct mtd_info *mtd, unsigned int page,
    const uint8_t *buf, int oob_required) {
    // 1. 检查该虚拟块是否已经在 bbt 中
    if (check_BBT(mtd, page/ppb) == 0) {
    // 从 bbt 中找一个空闲条目
    for (i = 0; i < RBA; i++) {
    if (rtkn->bbt[i].bad_block == BB_INIT && rtkn->bbt[i].remap_block != RB_INIT) {
    rtkn->bbt[i].bad_block = block; // 虚拟块号
    block_remap = rtkn->bbt[i].remap_block; // 选的备用块
    break;
    }
    }
    // 更新 NAND 上的 bbt 表
    rtk_update_bbt(mtd, rtkn->bbt);
    }
    // 2. 擦除备用块所在的物理块
    rtk_scan_erase_bbt(mtd, block_remap * ppb);
    // 3. 将原坏块中未损坏的页面数据拷贝到备用块
    for (i = 0; i < backup_offset; i++) {
    rtk_scan_read_oob(mtd, rtkn->tmpBuf, block * ppb + i, page_size);
    rtk_scan_write_bbt(mtd, block_remap * ppb + i, page_size, ...);
    }
    // 4. 将本次要写入的数据写入备用块对应页面
    rtk_scan_write_bbt(mtd, block_remap * ppb + backup_offset, ...);
    // 5. 将原坏块标记为坏(擦除并写 OOB 标记)
    rtk_scan_erase_bbt(mtd, block * ppb);
    rtk_scan_write_bbt(mtd, block * ppb, ...); // 写入坏块标记
    }

虚拟地址到物理地址的转换

复制代码
int rtkn_bbt_get_realpage(struct mtd_info *mtd, unsigned int page) {
    unsigned int ppb = mtd->erasesize / mtd->writesize;
    unsigned int block = page / ppb;          // 虚拟块号
    unsigned int page_offset = page % ppb;
    // 先通过 v2r 映射得到物理块号(跳过工厂坏块)
    unsigned int realblock = rtkn->bbt_v2r[block].block_r;
    // 再通过 bbt 检查该物理块是否被重映射
    for (i = 0; i < RBA; i++) {
        if (rtkn->bbt[i].bad_block == block) {
            realblock = rtkn->bbt[i].remap_block;  // 使用备用块
            break;
        }
    }
    return realblock * ppb + page_offset;
}

static int rtk_block_isbad(struct mtd_info *mtd, u16 chipnr, loff_t ofs) {
    page = (ofs >> this->page_shift);
    if (isLastPage) page = block*ppb + (ppb-1);
    rtk_scan_read_oob(mtd, rtkn->buf, page, page_size);
    block_status_p1 = rtkn->buf[page_size + OOB_BBI_OFF];
    if (block_status_p1 != 0xff) return -1;
    else return 0;
}

初始化流程

|

+-- rtknflash_lowinit()

+-- rtknflash_ops_init()

+-- nand_scan()

+-- rtkn_scan_bbt() (挂载的 scan_bbt)

|

+-- if (REMAP)

| +-- rtk_scan_v2r_bbt()

| | +-- create_v2r_remapping() // 创建虚拟->物理映射

| +-- rtk_nand_scan_bbt()

| +-- scan_last_die_BB() // 获取备用块

| +-- rtk_create_bbt() // 扫描坏块,建立重映射表

|

+-- if (NORMAL)

+-- nand_scan_normal_bbt()

+-- scan_normal_BB() // 扫描坏块,记录到 bbt_nor

坏块在各分区中的分布

  • REMAP BBT :坏块被重映射到备用块(分布在芯片尾部),因此各分区的 虚拟地址空间 是连续的,坏块不会造成分区空洞。

  • NORMAL BBT:坏块保留在原位,分区会被坏块分割。例如 boot 分区内部若存在坏块,则 MTD 会跳过它,导致该分区的有效数据不连续。

  • 驱动不提供按分区调整坏块分布的功能,依赖 MTD 层的 mtd->block_isbadmtd->block_markbad 实现跳过

分区表与坏块分布

4.1 分区表设置示例 (rtknflash_openwrt.c)

复制代码
static struct mtd_partition rtl8196_parts1[] = {
    {name: "boot", offset: 0, size: 0x500000},        // 5MB
    {name: "setting", offset: 0x500000, size: 0x300000},  // 3MB
    {name: "linux", offset: 0x800000, size: 0x600000},    // 6MB
    {name: "rootfs", offset: 0xe00000, size: 0x1A00000},  // 26MB
};

4.2 坏块在分区中的分布

坏块管理是全局的,跨越所有分区

每个分区看到的都是连续的"好块"空间

实际物理块可能分散在不同位置

坏块被映射到RBA(Remap Block Area)

5. 备用块管理(RBA)

5.1 RBA大小计算
从代码分析,RBA(备用块区域)通常位于Flash末尾:

复制代码
#ifdef CONFIG_RTK_REMAP_BBT
static int RBA;  // 重映射块区域大小
static unsigned int RBA_PERCENT = 5;  // 默认5%的块作为备用块

5.2 备用块地址分析

复制代码
// 假设总块数为 total_blocks
RBA = (total_blocks * RBA_PERCENT) / 100;

// 备用块位于Flash末尾
// 例如:总块数1024,RBA_PERCENT=5%,则:
// RBA = 1024 * 5 / 100 = 51.2 ≈ 51个块
// 备用块地址范围:[total_blocks - RBA, total_blocks-1]

6. 映射表写入NAND流程

6.1 写入BBT到Flash

复制代码
int rtk_scan_write_bbt(struct mtd_info *mtd, int page, size_t len,
                       uint8_t *buf, uint8_t *oob)
{
    // 1. 准备写入数据
    struct mtd_oob_ops ops;
    ops.mode = MTD_OOB_PLACE;
    ops.ooboffs = 0;
    ops.ooblen = mtd->oobsize;
    ops.datbuf = buf;
    ops.oobbuf = oob;
    ops.len = len;
    
    // 2. 写入到指定页面
    return mtd->write_oob(mtd, page, &ops);
}

6.2 BBT存储位置

主BBT存储在REMAP_BBT_POS (0x100000)

备份BBT存储在多个位置,由BACKUP_BBT定义

#define BACKUP_BBT 3 // 3个备份// 写入流程:

  1. 擦除BBT存储区域

  2. 写入主BBT

  3. 写入备份BBT到不同位置

  4. 验证写入结果

  5. 调整坏块比例和内存大小

7.1 当前设置(5%坏块比例)

在rtknflash.h中相关定义

复制代码
#define RBA_PERCENT 5    // 5%的块作为备用块
#define BBT_PERCENT 5    // BBT占用空间比例

// 计算RBA大小
RBA = (total_blocks * RBA_PERCENT) / 100;

7.2 调整为4%坏块比例

修改定义

复制代码
#define RBA_PERCENT 4    // 改为4%
#define BBT_PERCENT 4    // 相应调整BBT比例

/重新计算:

RBA = (total_blocks * 4) / 100; // 4%的块作为备用

示例:总块数1024

原RBA: 1024 * 5 / 100 = 51.2 ≈ 51块

新RBA: 1024 * 4 / 100 = 40.96 ≈ 41块

减少了10个备用块

7.3 调整分区表大小

当坏块比例从5%降到4%时,可用空间增加1%,需要重新计算分区大小:

原分区计算(5%坏块):

available_blocks = total_blocks * 0.95;

新分区计算(4%坏块):

available_blocks = total_blocks * 0.96;

每个分区可按比例调整:

例如rootfs分区原大小26MB,可调整为:

new_size = old_size * (0.96 / 0.95) ≈ old_size * 1.0105

  1. 功能函数详细分析

8.1 坏块标记函数

复制代码
int rtkn_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
    // 1. 获取块号
    unsigned int block = ofs >> this->bbt_erase_shift;
    
    // 2. 在OOB中标记坏块
    unsigned char marker = 0x00;  // 坏块标记
    return rtk_scan_write_oob(mtd, block, marker);
    
    // 3. 更新BBT
    rtkn_update_bbt(mtd, block, BBT_BLOCK_FACTORY_BAD);
}

8.2 坏块计数管理

坏块计数存储在rtknflash结构体中

复制代码
struct rtknflash {
    // ...
    unsigned int BBs;  // 坏块计数
    // ...
};

更新坏块计数

复制代码
void update_badblock_count(struct rtknflash *rtkn, int increment)
{
    rtkn->BBs += increment;
    
    // 检查是否超过RBA容量
    if (rtkn->BBs > RBA) {
        printk("ERROR: Bad blocks exceed RBA capacity!\n");
        // 处理错误:可能需要扩展RBA或报错
    }
}
  1. 内存大小调整建议

9.1 BBT内存分配

原分配(5%坏块):

bbt_size = (total_blocks * sizeof(BB_t) * 5) / 100;

新分配(4%坏块):

bbt_size = (total_blocks * sizeof(BB_t) * 4) / 100;

示例:1024块,BB_t大小8字节

原:1024 * 8 * 0.05 = 409.6字节

新:1024 * 8 * 0.04 = 327.68字节

节省约82字节内存

9.2 优化建议

动态调整RBA:根据实际坏块率动态调整

坏块预测:记录块擦写次数,预测可能变坏的块

磨损均衡:在RBA内实现简单的磨损均衡

压缩存储:对BBT进行压缩存储减少Flash占用

  1. 完整调整示例

要将坏块比例从5%调整为4%,需要修改:

  1. 修改rtknflash.h中的定义

    #define RBA_PERCENT 4 // 原为5
    #define BBT_PERCENT 4 // 原为5

  2. 重新计算分区大小

/在分区表定义中调整大小

复制代码
static struct mtd_partition rtl8196_parts1[] = {
    {name: "boot", offset: 0, size: 0x500000},
    {name: "setting", offset: 0x500000, size: 0x300000},
    {name: "linux", offset: 0x800000, size: 0x600000},
    // rootfs增加约1%空间
    {name: "rootfs", offset: 0xe00000, size: 0x1A80000},  // 增加0.8MB
};
  1. 重新编译驱动
相关推荐
小熊officer9 小时前
AMD架构与ARM架构
arm开发·架构
_kerneler1 天前
arm虚拟机实时性优化总结
arm开发
口袋里のInit1 天前
基础知识——ARM M核入栈出栈流程
开发语言·arm开发
2035去旅行2 天前
WIFI传输带宽
arm开发·嵌入式硬件
陌上花开缓缓归以2 天前
nand flash 驱动适配
arm开发
振南的单片机世界2 天前
影子寄存器:改ARR下个周期才生效,波形不突变
arm开发·stm32·单片机·嵌入式硬件
陌上花开缓缓归以3 天前
基于 W25N01KV 的 MTD/BBT/BMT/UBI 框架与坏块导致系统挂死问题剖析
arm开发
szxinmai主板定制专家3 天前
基于ZYNQ MPSOC图像采集与压缩系统总体设计方案
linux·arm开发·人工智能·嵌入式硬件·fpga开发
底层开发智库3 天前
学习ARM新姿势,理论实践的结合
arm开发