NAND/MTD/UBI/UBIFS概念及使用方法

发布时间 2023-09-16 18:11:27作者: ArnoldLu

关键词:NAND,MLC/SLC,Page/SubPage,EraseBlock,OOB,ECC,BitFlip,MTD,UBI,wearing-level,LEB,PEB,EC/VID,Volume,UBIFS等等。

 

由于调试NAND Linux启动,记录NAND/MTD/UBI/UBIFS点点滴滴。未去分析Kernel对应模块和相关工具代码,仅从使用角度去记录。

1. NAND/MTD/UBI/UBIFS整体框架

如下是NAND、MTD、UBI、UBIFS之间大概框架。

NAND颗粒芯片通过NAND Controller接入到SOC。

针对NAND硬件编写驱动接入MTD层,MTD屏蔽不同NAND颗粒差异,向上提供统一操作接口。

UBI基于MTD子系统,对NAND进行管理。

UBIFS基于UBI子系统实现文件系统功能。通过mount挂载分区,即可实现文件系统基本功能。

2. NAND/MTD/UBI/UBIFS相关概念

2.1 NAND硬件

一个NAND Flash由多个Block组成,每个Block又有多个Page组成。每个Page由有效数据区和OOB组成。部分NAND的Page分成多个SubPage。

NAND读写的最小单位是Page或SubPage。擦除最小单位是Block。

一种常见NAND Flash结构和容量计算如下:

 

常见的Nand Flash内部只有一个chip,每个chip只有一个plane。而目前有些复杂的容量更大的Nand Flash内部有多个chip,每个chip有多个plane。

概念上由大到小来说就是:Nand Flash  Chip  Plane  Block  Page  oob。

 一个典型16位总线宽度芯片的IO接口如下:

 更多参考:《Linux NAND Documentation)》。

2.1.1 NAND sub-pages

关于sub-page,不同NAND情况不同:

  • MLC NAND没有sub-page。
  • SLC NAND一般有sub-page。512字节页,由两个256字节sub-page组成。2048字节页由4个512字节sub-page组成。
  • SLC OneNAND的2048字节页由4个512字节sub-page组成。

sub-page仅在UBI层内使用,仅用于存放EC/VID头。UBI API不允许用户进行sub-page大小的IO操作。

2.2 MTD

MTD既不是block设备,也不是char设备。MTD用于描述符合raw flash特性的一类设备。

flash设备和block设备区别如下:

 更多参考:《Linux MTD FAQ)

MTD子系统为raw flash提供一个抽象层,不同flash使用同一套API。

2.3 UBI

UBI是一个针对raw flash设备的卷管理系统,在一个物理Flash设备上管理多个逻辑卷。

UBI卷是一组连续的LEB(Logical Erase Block),每个LEB动态映射到PEB(Physical Erase Block)上。

UBI提供全芯片磨损均衡,每个PEB一个擦除计数,将数据从一个破旧PEB移到较新PEB,做到均衡磨损管理。并做到透明的错误处理,包括bit-flip、坏块管理等。

UBI卷分为两类:dynamic和static。static卷为read-only,其数据通过crc32校验进行保护;dynamic卷为可读写,其数据一致性有上层负责,比如文件系统。

static卷一般用于kernel、initramfs、dtb。较大static卷由于要进行crc32校验,可能带来性能开销。

UBI识别坏块,从PEB备份池中选择新块,并将数据搬移到新块。

ECC校验纠正NAND位翻转。为了防止数据丢失,UBI从PEB备份池中选择新块,并做数据搬运。

UBI主要功能如下:

  • UBI提供的卷可以被动态创建、移除或改变大小。
  • UBI实现全设备的wear-leveling。
  • UBI负责坏块管理。
  • UBI通过scrubbing处理将数据丢失最小化。

UBI volume和MTD分区相似点:

  • 都是由擦除块组成:UBI volume由LEB组成,MTD分区由PEB组成。
  • 都支持:read、write、erase三个基本操作。

但是UBI volume有如下优点:

  • UBI负责wear-leveling,上层软件不需要关注。
  • UBI负责坏块管理。
  • UBI volume可以动态调整,MTD分区不支持。
  • UBI处理bit-flip。
  • UBI提供volume update功能,较容易检测到被中断的数据更新,并恢复。
  • UBI提供atomic logical eraseblock change ,降低了丢失数据可能性。
  • UBI提供un-map 操作,将LEB和PEB解绑。

UBI占用一些空间用于维护,包括:

  • 2 PEB存放volume table
  • 1 PEB预留给wear-leveling。
  • 1 PEB预留给atomic LEB change
  • 预留给坏块备份,一般占用20/1024。
  • UBI每个PEB开头保存EC和VID头。

定一些符号如下:

W - total number of physical eraseblocks on the flash chip (NB: the entire chip, not the MTD partition);

P - total number of physical eraseblocks on the MTD partition;

SP - physical eraseblock size;

SL - logical eraseblock size;

BB - number of bad blocks on the MTD partition;

BR - number of PEBs reserved for bad PEB handling (it is 20 * W/1024 for NAND by default, and 0 for NOR and other flash types which do not have bad PEBs);

B - MAX(BR,BB);

O - the overhead related to storing EC and VID headers in bytes, i.e. O = SP - SL.

所以由UBI引入导致的空间开销:(B + 4) * SP + O * (P - B - 4)。

如果将坏块计算在内,则空间开销为:(B - BB + 4) * SP + O * (P - B - 4)。

其中O对不同Flahs大小如下:

  • NOR为128字节。
  • 没有sub-page的NAND,O为2个页。
  • 对于有sub-page的NAND,EC和VID分别占用一个sub-page,共一个page。所以O为1个页。

更多参考《Flash space overhead》。

UBI如何判断一个块是坏块的?《Marking eraseblocks as bad》。

2.4 UBIFS

UBIFS工作在UBI volume上。UBI设备建立在MTD设备上,并向上提供UBI volume。

更多参考《UBIFS - UBI File-System》。

3. NAND/MTD/UBI/UBIFS相关工具以及代码

从NAND到UBIFS不同层级,对应的工具如下:

Linux下NAND/MTD/UBI/UBIFS相关功能配置:

Device Driver 
  -> Memory Technology Devide(MTD) support
    ->MTD test support--针对MTD的测试工具。
    ->Partition parsers
      ->Open Firmware(device tree) partitioning parser--分区信息通过DTS配置。     ->Cacheing block device access to MTD devices--基于RAM模拟出block设备。     ->Raw/Parallel NAND Device support。--Raw/Paralle设备驱动。     ->Enable UBI - Unsorted block images--使能UBI。
      ->UBI wear-leveling threshold--把擦写次数大的PEB放到used树上减少被擦写的机会,把擦写次数小的节点放到free树上增加被擦写的机会,这样就达到了擦写均衡的目的。
      ->Maximun expected bad eraseblock count per 1024 eraseblocks--Flash允许出现的最大坏块比例。
      ->UBI Fastmap--快速attach UBI设备。
      ->MTD devices emulation driver--在UBI volumen上模拟出MTD设备。
      ->Read-only block devices on top of UBI volumes--在UBI volumen上模拟出只读block设备。 File systems   ->Miscellaneous filesystems     ->UBIFS file system support--支持ubifs文件系统。

相关工具包括MTD操作设备以及ubifs文件系统操作在buildroot中配置:

Target packages
    ->Filesystem and flash utilities
        ->mtd, jffs2 and ubi/ubifs tools

3.1 NAND相关工具

Linux提供NAND模拟器nandsim,他基于RAM或一个文件来模拟NAND Flash,用于调试或者工具开发。

根据NAND Flash Table提供设备列表ID,在Full ID一列找到ID参数。FullID从左向右依次为first_id_byte、second_id_byte、third_id_byte、fourth_id_byte。

更多参考《How do I use UBIFS with nandsim?》。

modprobe nandsim first_id_byte=0x20 second_id_byte=0x33 - 16MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0x35 - 32MiB, 512 bytes page;
modprobe nandsim first_id_byte=0x20 second_id_byte=0xac third_id_byte=0x00 fourth_id_byte=0x15 - 512MiB, 2048 bytes page;
modprobe nandsim first_id_byte=0xec second_id_byte=0xd3 third_id_byte=0x51 fourth_id_byte=0x95 - 1GiB, 2048 bytes page;

更多参考《Linux NAND FAQ

3.2 MTD代码及相关工具

3.2.1 MTD子系统接口

MTD提供如下系统接口:

  • MTD字符设备接口/dev/mtdX,这些字符设备提供了到raw flash的IO访问。可以通过ioctl调用来擦除块,标记坏块、获取MTD信息等。
  • /proc/mtd提供MTD分区大小、擦除块大小、名称。
  • /sys/class/mtd/mtdX提供了MTD的完整信息。

/sys/class/mtd/mtdX提供了MTD相关的完整信息:

bad_blocks--MTD设备中被标记为坏块的数量。
bbt_blocks--被标记为reserved的块数,这些块存于BBT(Bad Block Table)。
bitflip_threshold--每一次ECC校准(ecc_step_size)内能纠正的bit数。如果错误bit数大于等于此值,则返回-EUCLEAN。
corrected_bits--ECC校准成功的bit数。
dev--主从设备号。
device -> ../../../xxx
ecc_failures--ECC报告的错误数量。
ecc_step_size--每次ECC校准的范围大小。
ecc_strength--每次ECC能够纠正的最大bit数。
erasesize--擦除块大小。
flags--在mtd-abi.h中定义,0x400对应MTD_WRITEABLE。
mtdblock16--基于此MTD设备创建的Block设备。
name--MTD分区名。
numeraseregions--支持可变大小擦除块的数目。不支持则为0。
of_node -> ../../../../../firmware/devicetree/base/xxx/partitions/partition@rootfs--对应的DTS节点。
offset--相对于整个芯片开始的偏移字节数。
oobavail--OOB区域可存放数据的大小。
oobsize--OOB区域大小。
power
size--MTD分区大小。
subpagesize--subpage大小。
subsystem -> ../../../../../class/mtd
type--Flash类型。
uevent
writesize--写大小,对应page。

3.2.2 MTD相关工具

flashcp/flash_erase

flashcp将文件写入到MTD分区;flash_erase擦除指定分区。

flashcp rootfs.ubi /dev/mtd16  将文件写入到MTD分区。
flash_erase /dev/mtd16 0 256  擦除MTD设备指定起始块指定大小块数。 

mtd_debug

mtd_debug用于获取mtd设备的信息,擦除/读取/写入等操作。

mtd_debug info <device>
       mtd_debug read <device> <offset> <len> <dest-filename>
       mtd_debug write <device> <offset> <len> <source-filename>
       mtd_debug erase <device> <offset> <len>

示例:

mtd_debug info /dev/mtd16  获取MTD设备信息。
mtd_debug erase /dev/mtd16 0 0x2000000 擦除MTD设备从0开始的0x2000000范围。 mtd_debug write /dev/mtd16 0 7471104 rootfs.ubi 将rootfs.ubi文件的一定大小写入到MTD分区0地址处。 mtd_debug read /dev/mtd16 0 7471104 rootfs.ubi-2 读取MTD分区0地址开始一定大小到rootfs.ubi-2。

mtdpart

增加或者删除MTD分区。

mtdpart add [OPTION] <MTD_DEVICE> <PART_NAME> <START> <SIZE>
mtdpart del [OPTION] <MTD_DEVICE> <PART_NUMBER>

nanddump/nandwrite/nandtest

nanddump从NAND MTD设备中读取数据;nandwrite将数据写入到MTD设备中;nandtest对MTD设备进行读写测试。

nandwrite /dev/mtd16 rootfs.ubi  将rootfs.ubi文件写入MTD分区。
nanddump /dev/mtd16 -s 0 -l 7733248 -f rootfs.ubi-nanddump -o  读取MTD分区指定起始位置特定大小数据到-f指定文件中。并且读取OOB数据。

mtdinfo

mtdinfo用于显示单个或者所有MTD分区的信息。

-u, --ubi-info                  如果没有创建UBI信息,则显示可能的信息;如果已经创建UBI,则显示当前UBI信息。
-M, --map 显示擦除块序号和地址。 -a, --all 显示所有MTD设备信息。

示例mtdinfo /dev/mtd16 -u -M:

mtd16
Name:                           rootfs--MTD设备名。
Type:                           nand--MTD设备类型。
Eraseblock size:                131072 bytes, 128.0 KiB--擦除块大小。
Amount of eraseblocks:          256 (33554432 bytes, 32.0 MiB)--擦除块总数。
Minimum input/output unit size: 2048 bytes--Page大小。
Sub-page size:                  512 bytes--Sub-page大小。
OOB size:                       64 bytes--OOB大小。
Character device major/minor:   90:32
Bad blocks are allowed:         true
Device is writable:             true
Default UBI VID header offset:  512--默认VID头偏移,实际可能是2048。
Default UBI data offset:        2048--默认UBI数据偏移,实际可能是4096。
Default UBI LEB size:           129024 bytes, 126.0 KiB--默认LEB大小,实际可能是124KB。
Maximum UBI volumes count:      128--最大UBI volume数量。
Eraseblock map:
   0: 00000000           1: 00020000           2: 00040000           3: 00060000
...
 252: 01f80000         253: 01fa0000         254: 01fc0000         255: 01fe0000

MTD相关测试工具

flash_speed对MTD设备进行读/擦除速率测试。

flash_stress压力测试执行任意读写擦除,验证MTD设备IO功能。

flash_torture对同一区域进行擦除,写操作,看是否出现坏块

nandpagetest对MTD设备擦除,读写等测试。nandsubpagetest对MTD分区的Sub-Page进行读写验证测试。

如下使用nandsubpage测试发现出现位反转:

erasing good eraseblocks
verifying all eraseblocks for 0xff
verified 16 eraseblocks
writing first 2 sub-pages on PEB 0...
verifying first 2 sub-pages of PEB 1
error: verify failed at PEB 1, offset 0
------------- written----------------
...
7f95857c547d73c1b635896dbc9f0bad67e2d9cb7350a4f7f104e0c5964c7874
...
------------- read ------------------
...
7f95857c547d73c1b635896dbc9f0bad67e2d9cb7350a4f7f184e0c5964c7874
...
-------------------------------------

nandflipbits构造BitFlip;nandbiterrs引入bit错误并检查ECC能否对多位错误恢复。

3.2.3 关于/dev/mtdX和/dev/mtdblockX之间区别?

The mtdblock driver对mtdblock和mtd直接区别做了简单介绍:

  • mtdblock是在MTD设备上模拟块设备。本身没有坏块管理。
  • mtdblock将整个擦除块缓存在RAM中,修改,擦除,然后将修改内容写回。
  • mtdblock不尝试做优化,并且断电可能丢失较多数据。
  • 不做wear-leveling和bit-flip处理。

不建议使用/dev/mtdblockX,如果有RO情况,建议使用UBI RO Volume。

3.3 UBI

3.3.1 UBI头

UBI在每个PEB开始存放两个64字节的头:

  • erase counter header(EC header):保存PEB的擦除计数等信息,以实现磨损平衡等维护操作。
  • volume indentifier header(VID header):保存volumen ID,以及LEB和PEB的映射关系。

当UBI附着MTD设备时,会扫描所有的块,并做如下操作:读取头,进行crc32校验。保存擦除次数以及逻辑块到物理块映射信息。

struct ubi_ec_hdr {
    __be32  magic;--魔数0x55424923。
    __u8    version;
    __u8    padding1[3];
    __be64  ec;--擦除计数。
    __be32  vid_hdr_offset;--VID头起始地址偏移量。
    __be32  data_offset;--用户数据起始地址偏移量。
    __be32  image_seq;--一串数字表示已经准备给UBI使用,所有的块为同一个值。
    __u8    padding2[32];
    __be32  hdr_crc;--整个EC头的crc32校验值。
} __packed;

当一个PEB和LEB关联时,将VID头写入PEB中。映射或者写入数据到未映射LEB时,UBI找到一个合适的PEB,将VID头写入。

当去映射时,解除LEB和PEB映射关系,并将PEB调度去擦除。

struct ubi_vid_hdr {
    __be32  magic;--魔数0x55424921。
    __u8    version;--目前为1。
    __u8    vol_type;--1是dynamic,0是staci类型的volume。
    __u8    copy_flag;
    __u8    compat;
    __be32  vol_id;--PEB所属的volume ID。
    __be32  lnum;-映射的LEB序号。
    __u8    padding1[4];
    __be32  data_size;--当前LEB包含的字节数。
    __be32  used_ebs;--当前volume使用的LEB数量。
    __be32  data_pad;--当前PEB结尾未使用的字节数。
    __be32  data_crc;--LEB中保存数据的crc校验和。
    __u8    padding2[4];
    __be64  sqnum;
    __u8    padding3[12];
    __be32  hdr_crc;--此VID头的crc校验和。
} __packed;

简单说PEB = LEB + EC header + VID header:

EC和VID头在不同的Flash位置如下:

更多参考《UBI headers position》。

 3.3.2 UBI sysfs

/dev/ubi_ctrl为UBI层提供对MTD设备attach/detacch的操作节点。

/dev/ubi0对应UBI设备0,Kernel为其创建了后台线程ubi_bgt0d。UBI后台线程主要做如下工作:

  • 后台PEB擦除。
  • torture有缺陷的PEB,判断是否为坏块。
  • 将数据从磨损PEB转移到较新PEB。
  • 将数据从存在位翻转PEB移出。

更多参考《What does the "ubi_bgt0d" thread do?

/dev/ubi0_0对应UBI设备0的Volume 0。

/sys/kernel/debug/ubi存放不同UBI设备的调试接口,子目录表示一个UBI设备:

chk_fastmap
chk_gen
chk_io
detailed_erase_block_info
tst_disable_bgt
tst_emulate_bitflips
tst_emulate_io_failures
tst_emulate_power_cut
tst_emulate_power_cut_max
tst_emulate_power_cut_min

如何调试UBI子系统?《How do I debug UBI?

如何加速UBI初始化《How do I speed up UBI initialization

3.3.3 UBI工具

用户空间相关工具:

ubiattach/ubidetach

ubiattach将一个MTD设备挂载到UBI子系统。

-d, --devn=<number>   指定创建的UBI设备序号。
-p, --dev-path=<path> 对应MTD设备的/dev/mtdX路径。 -m, --mtdn=<number> MTD设备序号。
-O, --vid-hdr-offset 指定存放VID头的偏移量。
-b, --max-beb-per1024 每1024块中允许出现的坏块数。

如果不特别指定UBI控制设备名称,默认为/dev/ubi_ctrl。

示例:

ubiattach -p /dev/mtd0 将/dev/mtd0附着到UBI子系统。
ubiattach -m 0      将/dev/mtd0附着到UBI子系统。
ubiattach -m 0 -d 3 将/dev/mtd0附着到UBI子系统,并创建/dev/ubi3。
ubiattach -m 1 -b 25 将/dev/mtd1附着到UBI子系统,并保留25*C/1024个擦除块作为坏块处理保留。C是Flash整个芯片的擦除块数量。

ubidetach则是将MTD设备从UBI子系统移除。

ubiformat

ubiformat按照UBI规则对MTD设备进行格式化。还可以根据UBI镜像内容进行格式化。

-s, --sub-page-size=<bytes>  指定subpage大小。
-O, --vid-hdr-offset=<offs> 指定VID头的偏移。不指定情况下,UBI根据是否存在subpage选择。如果没有subpage,则offset为一个page;如果存在subpage,则offset为一个subpage。
-f, --flash-image=<file> 指定输入的UBI镜像文件。
-S, --image-size=<bytes> 指定输入大小。
-e, --erase-counter=<value> 指定UBI EC头中擦除计数。
-Q, --image-seq=<num> 指定UBI EC头中image sequence number。

ubinfo

ubinfo显示指定或所有UBI设备信息。

UBI version:                    1
Count of UBI devices:           1
UBI control device major/minor: 10:62
Present UBI devices:            ubi0

ubi0
Volumes count:                           1
Logical eraseblock size:                 126976 bytes, 124.0 KiB
Total amount of logical eraseblocks:     256 (32505856 bytes, 31.0 MiB)
Amount of available logical eraseblocks: 0 (0 bytes)
Maximum count of volumes                 128
Count of bad physical eraseblocks:       0
Count of reserved physical eraseblocks:  20
Current maximum erase counter value:     2
Minimum input/output unit size:          2048 bytes
Character device major/minor:            249:0
Present volumes:                         0

Volume ID:   0 (on ubi0)
Type:        dynamic
Alignment:   1
Size:        232 LEBs (29458432 bytes, 28.0 MiB)
State:       OK
Name:        rootfs
Character device major/minor: 249:1

ubinize

根据配置文件创建多个volume,以及命令行参数指定NAND特性创建UBI镜像。

-o, --output=<file name>     输出镜像文件名。
-p, --peb-size=<bytes>       PEB大小,即eraseblock大小。
-m, --min-io-size=<bytes> 即为page大小,即使存在subpage,UBI层的IO操作也以page为单位。仅在VID是使用subpage。
-s, --sub-page-size=<bytes> 如果存在subpage,指定为subpage大小。不存在可以忽略。
-O, --vid-hdr-offset=<num> 仅在VID头操作的时候使用此参数,可以指定为subpage大小。也可以指定为page大小。Linux bootargs中需要匹配。
-e, --erase-counter=<num> EC头中的擦除计数初始值。
-Q, --image-seq=<num> 固定EC投中image sequence number值。

mkfs.ubifs和ubinize配合使用,mkfs.ubifs创建ubifs镜像,ubinize适配特定NAND规格生成可以裸机烧录的UBI镜像。

mkfs.ubifs -d <input dir> -e 0x1f800 -c 128 -m 0x800 -x none -o rootfs.ubifs
ubinize -vv -o rootfs.ubi -m 2048 -p 128KiB -s 512 ubinize.cfg

其中ubinize.cfg配置如下:

[ubifs]

mode=ubi
vol_id=0--volume ID。
image=rootfs.ubifs--输入镜像。 vol_size=16MiB--vol_size的计算不一定准确,尤其不同NAND的坏块数量不一致。vol_size代表对应volumen所应该提供的最小大小。可以排除vol_size选项,在UBI启动时根据NAND可用大小自动计算vol_size,此时需打开autoresize选项。 vol_type=dynamic--static为只读,dynamic为读写。 vol_name=rootfs--volume名称。 vol_flags=autoresize--UBI在初次运行时会将剩余可用擦除块给volume使用。关于autoresize参考《
Volume auto-resize》。
vol_alignment=1

更多参考《How do I create UBI images?

UBI volume管理工具:ubimkvol/ubirmvol/ubirsvol/ubiupdatevol/ubirename

ubimkvol在UBI设备上创建UBI volume;ubirmvol从UBI设备上移除一个UBI volume;ubirsvol调整一个UBI volume大小;ubiupdatevol更新数据到UBI volume中;ubirename修改UBI设备上volume名称。

ubiblock

ubiblock在UBI volume上创建或删除块设备。

-c, --create               create block on top of a volume
-r, --remove               remove block from volume

示例:

ubiblock --create /dev/ubi0_0

3.4 UBIFS

3.4.1 UBIFS sysfs

一个UBIFS volume通过mount挂载后,Kernel为每个ubifs创建一个后台线程ubifs_bgtX_Y。X是UBI设备号,Y是UBI volume ID。

ubifs_bgtX_Y主要做如下优化操作:

  • 后台日志提交。
  • 刷写缓存。
  • 后台垃圾回收工作。

更多参考《What does the "ubifs_bgt0_0" thread do?》。

ubifs提供了一系列工具用于调试定位

  • Flash模拟器nandsim。
  • 动态调试消息,需打开CONFIG_DYNAMIC_DEBUG。
  • /sys/kernel/debug/ubifs对去全局UBIFS生效,子目录下配置仅对对应ubifs生效。

更多参考《How do I debug UBIFS?》。

3.4.2 UBIFS工具

mount/umount

当一个UBI volume包含UBIFS时,可以通过mount将其挂载:

mount -t ubifs ubi0:rootfs /temp--rootfs为UBI volume名称,其位于UBI设备0。
mount -t ubifs /dev/ubi0_0 /tmp

mkfs.ubifs

mkfs.ubifs:以一个目录作为输入,创建UBIFS文件系统镜像。

常用选项有:

Options:
-r, -d, --root=DIR       指定创建文件系统的输入目录。
-m, --min-io-size=SIZE 最小IO字节大小,为页大小。虽然对于存在subpage的NAND最小IO为subpage,但是除了VID头使用。UBI层其他IO操作使用的还是Page。
-e, --leb-size=SIZE LEB擦除块大小,等于PEB-EC/VID占用大小。其中EC/VID大小:对于NOR为128B;对于无subpage NAND为2Page;对于有subpage NAND为2subpage,但是一般占用整个page,可以选择不适使用subpage,则为2page。 -c, --max-leb-cnt=COUNT 最大LEB数量。参考《
What is the purpose of -c (--max-leb-cnt) mkfs.ubifs option?》。
-o, --output=FILE 镜像输出文件名。
-j, --jrn-size=SIZE 日志大小。
-x, --compr=TYPE 可选择的压缩算法:"lzo", "favor_lzo", "zlib", "zstd" or "none",默认为"lzo"。
-X, --favor-percent 仅在favor_lzo被选中时生效,如果zlib优于lzo超过此百分数则选择zlib,否则选择lzo。参考《What is "favor LZO" compression?》。
-F, --space-fixup 由于Flash烧录器无法对全0xFF空间处理,通过-F对全0xFF空间加flag。Linux在第一次挂载的时候写入有效的UBI数据。参考《What is the the purpose of the -F (--space-fixup) mkfs.ubifs option?

 示例:

mkfs.ubifs -r ubifs/target -e 0x1f000 -c 255 -m 0x8000 -x lzo -F -o rootfs.ubifs--以ubifs/target作为输入,创建2KB页,LEB为0x1f000(去掉2page),最多256LEB,且压缩算法为lzo,对0xFF页标记的ubifs文件系统,输出为rootfs.ubifs。
mkfs.ubifs -r /opt/img /dev/ubi0_0--在/dev/ubi0_0这个UBI volume上创建以/opt/img作为输入的ubifs文件系统。
mkfs.ubifs /dev/ubi0_0--在/dev/ubi0_0这个UBI volume上创建一个空的ubifs文件系统。

4 Linux/Buildroot关于NAND/UBI/UBIFS启动配置

构建UBI/UBIFS镜像参考《How do I create UBI images?》《How do I create an UBIFS image?》《How do I mount UBIFS?

4.1 Kernel bootargs设置

Linux bootargs选择NAND作为rootfs启动设备:

console=ttyAMA0,115200 ubi.mtd=rootfs,2048 root=ubi0:rootfs rw rootfstype=ubifs rootwait loglevel=16 earlyprintk

ubi.mtd=rootfs.2048表示将rootfs对应的MTD设备attach到UBI,并且强制VID头offset为2048。如果不设置2048,则Kernel根据NAND配置自动选择:存在subpage,则offset为一个subpage;不存在subpage,则offset为一个page。

root=ubi0:rootfs表示挂载名称为rootfs的UBI volume作为跟文件系统。

rootfstype=ubifs表示文件系统类型为ubifs。

rw表示rootfs为可读写。

rootwait表示kernel会一直等待rootfs可用,才会继续启动。相对rootdelay=x,kernel只会等待固定时间。

另外根据需要还可以增加rootflags=sync,告诉内核已同步方式挂载文件系统,降低意外断电造成文件异常。代价是会影响文件系统性能。

4.2 Buildroot配置

在Buildroot中配置:

 │ │    [*] ubi image containing an ubifs root filesystem  配置生成rootfs.ubi镜像,可以通过Flash烧录器裸数据烧录。
  │ │    (0x20000) physical eraseblock size (NEW)          物理擦数块大小,由NAND物理特性决定。即PEB大小。
  │ │    (512) sub-page size (NEW)                         sub-page大小。如果没有可以填写page大小。
  │ │    [*]   Use custom config file                      使用ubinize配置文件。
  │ │    (ubinize.cfg) Configuration file path             
  │ │    ()    Additional ubinize options (NEW)            
  │ │    -*- ubifs root filesystem                         配置生成rootfs.ubifs镜像,是UBIFS格式。
  │ │    (0x1f000) logical eraseblock size                 LEB = PEB - EC/VID。对于没有subpage情况,LEB=PEB - 2Page;对于使用subpage存放VID情况,LEB = PEB - 1Page;对于不使用subpage存放VID情况,LEB = PEB - 2Page。Kernel bootargs需要匹配。
  │ │    (0x800) minimum I/O unit size                     即为Page大小。
  │ │    (2048) maximum logical eraseblock count           ubifs最大LEB数量。
  │ │          ubifs runtime compression (lzo)  --->       默认压缩算法。
  │ │          Compression method (no compression)  --->   
  │ │    (-F)  Additional mkfs.ubifs options               在Linux第一次attach时对全0xFF块特殊处理。

 其中ubinize.cfg配置如下:

[ubifs]
mode=ubi
vol_id=0
vol_type=dynamic
vol_name=rootfs
vol_alignment=1
vol_flags=autoresize
image=BR2_ROOTFS_UBIFS PATH

 如何确定LEB,min IO,subpage大小?参考《How do I find out min. I/O unit size, sub-page size, and LEB size?》。

对于有subpage的NAND,使用UBI时忽略subpage的方法:《How do I force UBI to ignore sub-pages?》。

4.3 Linux环境下镜像制作

在一个启动的Linux环境中烧录UBIFS相对简单:

ubiformat /dev/mtdX
ubiattach -p /dev/mtdX
ubimkvol /dev/ubi0 -N volume_name -s 64MiB
ubiupdatevol /dev/ubi0_0 /path/to/ubifs.img
mount -t ubifs ubi0:volume_name /mount/point

5 常见问题及解决方法

5.1 bad VID header offset 2048, expected 512

日志如下:

[  415.728860] 000: ubi0 error: validate_ec_hdr: bad VID header offset 2048, expected 512
ubiattach: error!: cannot attach mtd16
           error 22 (Invalid argument)

解决方法:

ubinize -vv -o ubi.img -m 2048 -p 128KiB -s 512 ubinize.cfg

在ubinize创建ubi.img时,-s设置subpange的大小。

NAND设备subpage的参数可以在/sys/class/mtd/mtdxx/subpage中获取。

5.2 MTD device 15 is write-protected, attach in read-only mode

日志如下:

[  382.730705] 000: ubi0: attaching mtd15
[  382.730913] 000: ubi0: MTD device 15 is write-protected, attach in read-only mode
[  382.817059] 000: ubi0: scanning is finished
[  382.817328] 000: ubi0 error: ubi_read_volume_table: the layout volume was not found
ubiattach: error!: cannot attach mtd15
           error 22 (Invalid argument)
# [  382.818428] 000: ubi0 error: ubi_attach_mtd_dev: failed to attach mtd15, error -22

 解决方法:

在dts分区中去掉read-only配置。

5.3 ubi_io_read: error -74 (ECC error) while reading 126976 bytes from PEB 47:4096, read 126976 bytes

出现了ECC校验错误,可能原因有:

  • NAND驱动问题
  • HW问题
  • 烧录的UBI/UBIFS镜像问题
    • ECC校验算法错误
    • free space fixup

更多参考《I see this UBI error: "ubi_io_read: error -74 (ECC error) while reading 126976 bytes from PEB 47:4096, read 126976 bytes"》。

5.4 避免UBIFS丢失数据的措施

UBIFS write-buffer是文件系统内部实现的,介于page缓存和Flash之间。可以通过这些方法对齐进行同步,参考《UBIFS write-buffer》。

避免断电重启的后出现空文件的方法,参考《Why is my file empty after an unclean reboot?》《Why does my file have zeroes at the end after an unclean reboot?》《UBIFS in synchronous mode vs JFFS2》。

5.5 是使用MTD分区还是使用UBI volume

一个MTD分区对应一个UBI设备,分区的空间越大,UBI做磨损均衡越高效。

所以多使用UBI volume,而不是MTD分区。

但是某些分区必须使用MTD分区,主要是因为读取这些分区的软件无法处理UBI volume。

下面为两个对比示例:

 上图出现4个MTD分区。下图做了如下修改:

  • 将ubi1:log转移到ubi0上。
  • 修改jffs2为ubifs,并移动到ubi0上。
  • 合并1/2/3 MTD分区。

 更多参考《UBI · EmbeddedSystem (gitbooks.io)》。

6 uboot下NAND/MTD/UBI/UBIFS命令

nand

mtd

ubi

ubifsload

ubifsls

ubifsmount

ubifsumount