NAND/MTD/UBI/UBIFS概念及使用方法
关键词: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。注意这里预留坏块数=(20/1024)*整个Flash块数。具体参考:《Reserved blocks for bad block handling (only for NAND chips)》。
- 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. Linux下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设备驱动。
->SPI NAND device support--SPI接口NAND设备驱动。
->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文件系统。
不同压缩算法对rootfs/ubifs的测试结果:
压缩算法 | rootfs.ubifs大小(B) |
None | 20570112 |
gzip | 11427840 |
lzo | 12443648 |
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。
更多参考《How do I force UBI to ignore sub-pages?"》。
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
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命令
uboot下关于NAND配置如下:
Device Drivers ->MTD Support ->Enable MTD layer--支持MTD栈,提供对NNAD、NOR等设备的统一接口。 ->Raw NAND Device Support--PPI接口类型的NAND驱动。 ->SPI Flash Support--SPI接口类型的NAND驱动。 ->GigaDevice SPI flash support ->UBI support--UBI层支持。 ->MMC Host controller Support ->MMC/SD/SDIO card support--支持MMC、SD、SDIO。 ->Support for MMC/SD write operations ->SPI Support ->Cadence QSPI driver--SPI接口驱动支持。
uboot下MTD相关代码
drivers/mtd/ ├── altera_qspi.c ... ├── mtd-uclass.c ├── nand--NAND驱动。 │ ├── bbt.c │ ├── core.c │ ├── Kconfig │ ├── Makefile │ ├── raw--PPI接口NAND驱动以及通用层。 │ │ ├── fsl_elbc_nand.c ... │ │ └── zynq_nand.c │ └── spi--SPI接口NAND驱动。 │ ├── core.c ... │ └── winbond.c ├── nvmxip--将NVM XIP Flash上模拟成块设备。 ├── onenand--OneNAND类型存储驱动。 ├── renesas_rpc_hf.c ├── spi--SPI接口驱动。 │ ├── fsl_espi_spl.c ... │ └── spi-nor-tiny.c ├── ubi--UBI层业务和命令实现。 │ ├── attach.c ... │ └── wl.h └── ubispl ├── Makefile ├── ubispl.c ├── ubispl.h └── ubi-wrapper.h
uboot下SPI NAND/PPI NAND/SD初始化入口
init_sequence_r ->initr_nand ->nand_init--RAW/PPI接口NAND初始化。 ->nand_init_chip ->board_nand_init--特定平台NAND接口初始化,以及struct nand_chip初始化。 ->nand_scan--查找NAND设备。 ->nand_register ->create_mtd_concat ->initr_mmc
6.1 NAND
nand命令针对裸NAND设备,显示信息、读写擦除、坏块等管理。
nand info - 显示nand可用nand设备,其中包括nand名称、Page、OOB、Erase、subpage等信息。 nand device [dev] - 显示或者设置当前NAND设备。 nand read - addr off|partition size - 从NAND off偏移读取size字节到addr中。跳过坏块。 nand write - addr off|partition size - 写从addr开始size字节数据到NAND off处。跳过坏块。 nand read.raw - addr off|partition [count] - 类似于nand read,不跳过坏块。 nand write.raw[.noverify] - addr off|partition [count] - raw类似于nand write,不跳过坏块。 Use read.raw/write.raw to avoid ECC and access the flash as-is. nand erase[.spread] [clean] off size - erase 'size' bytes from offset 'off' With '.spread', erase enough for given file size, otherwise, 'size' includes skipped bad blocks. nand erase.part [clean] partition - 擦除整个MTD分区。nand erase.chip [clean] - 擦除整个芯片。nand bad - 显示坏块。 nand dump[.oob] off - 不带oob则只dump page内容,带oob则dump page+oob内容。 nand scrub [-y] off size | scrub.part partition | scrub.chip really clean NAND erasing bad blocks (UNSAFE) nand markbad off [...] - mark bad block(s) at offset (UNSAFE) nand biterr off - make a bit error at offset (UNSAFE)
6.2 MTD
mtdparts
mtdparts用于uboot下增加、删除、显示MTD分区。
mtdparts - 显示分区表信息。 mtdparts delall - 删除所有分区表。 mtdparts del part-id - 删除个别分区表,比如part-id = nand0,1。 mtdparts add <mtd-dev> <size>[@<offset>] [<name>] [ro] - 增加分区信息。 mtdparts default - 复位使用默认分区信息。
在uboot下配置MTD分区:
Command line interface ->Filesystem commands ->MTD partition support ->Default MTD IDs--默认mtdids:nand0=nandflash,表示nand设备0使用nandflash分区表。 ->Default MTD partition scheme--默认分区表,<分区表名>:<分区大小>(分区名称),<分区大小>(分区名称),...。
mtd
列出MTD分区,以及对MTD设备进行读写擦除等操作。
mtd list mtd read[.raw][.oob] <name> <addr> [<off> [<size>]] mtd dump[.raw][.oob] <name> [<off> [<size>]] mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]] mtd erase[.dontskipbad] <name> [<off> [<size>]]
6.3 UBI
ubi
UBI设备attach/detach,以及基于卷的续写。
ubi detach - detach ubi from a mtd partition ubi part [part] [offset] - Show or set current partition (with optional VID header offset) ubi info [l[ayout]] - Display volume and ubi layout information ubi check volumename - check if volumename exists ubi create[vol] volume [size] [type] [id] [--skipcheck] - create volume name with size ('-' for maximum available size) ubi write[vol] address volume size - Write volume from address with size ubi write.part address volume size [fullsize] - Write part of a volume from address ubi read[vol] address volume [size] - Read volume to address with size ubi remove[vol] volume - Remove volume ubi skipcheck volume on/off - Set or clear skip_check flag in volume header
6.4 UBIFS
ubifsmount/ubifsumount
挂载和去挂载UBIFS:
ubifsload <addr> <filename> [bytes] - load file 'filename' to address 'addr' ubifsumount - unmount current volume
ubifsls
显示挂载的ubifs内容:
ubifsls - list files in a directory
ubifsload
ubifsload <addr> <filename> [bytes] - load file 'filename' to address 'addr'
挂载并操作ubifs文件系统流程:
ubi part rootfs 2048--设置当前分区使用rootfs。 ubifsmount ubi0:rootfs--挂载rootfs文件系统。 ubifsls--显示rootfs根目录。
ubifsls /bin--显示bin目录
读取文件内容:
ubifsload 0x80000000 /root/test.bin--将test.bin文件加载到0x80000000。
7 感谢
感谢《Memory Technology Device (MTD) Subsystem for Linux. (infradead.org)》提供了详细、丰富的关于NAND以及Linux下MTD/UBI/UBIFS知识。