驱动支持两种坏块管理模式:
-
REMAP BBT (
CONFIG_RTK_REMAP_BBT):虚拟块到物理块的映射 + 备用块重映射。 -
NORMAL BBT (
CONFIG_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_PERCENT和BBT_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_isbad和mtd->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个备份// 写入流程:
-
擦除BBT存储区域
-
写入主BBT
-
写入备份BBT到不同位置
-
验证写入结果
-
调整坏块比例和内存大小
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
- 功能函数详细分析
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或报错
}
}
- 内存大小调整建议
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占用
- 完整调整示例
要将坏块比例从5%调整为4%,需要修改:
-
修改rtknflash.h中的定义
#define RBA_PERCENT 4 // 原为5
#define BBT_PERCENT 4 // 原为5 -
重新计算分区大小
/在分区表定义中调整大小
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
};
- 重新编译驱动