MTD versus block devices
There are several different kinds of embedded systems, especially today, since they're cheap. So, it's very important to know which device best fits our needs before starting the coding. As far as the name suggests, the embedded computers are embedded into the device they have to control or monitor. Often, these devices are placed into hostile environments: industrial plants (with dust and vibrations); open environment (extreme temperatures or rains); or aboard of trucks, cars, trains and other automotive systems.
In these scenarios, we have to carefully choose the hardware components that compose our embedded computer. Even if it's quite obvious, we cannot use a normal hard disk to store our data. More subtle is the fact that we cannot even choose a microSD! In fact, we can easily find these devices in every electronic store. However, they are not suitable for environments with vibrations (they are not soldered, and the contacts may get damaged) nor in places with very hot or cold temperatures (they usually are designed for standard, human-compatible environments). Also, they still have some problems regarding the device lifetime and possible corruption at power off. No way! We have to consider a different solution!
A possible (and relatively cheap) solution is to use flash memories. These special kinds of memories are obviously non-volatile, and they consist of one or more chips with very large temperature ranges that can be soldered on the board. The disadvantage in using these devices is that they cannot be accessed as a normal block device. That's why, in a Linux-based system, they are known as MTD devices.
What is an MTD device?
In Chapter 3 , C Compiler, Device Drivers, and Useful Developing Techniques, in Char, block, and net device section, we introduced block devices, that is, devices accessed in blocks of data and that support a filesystem. The MTD devices are based on the flash memory technology (NOR or NAND and other variants) that can be get accessed as a block device, where we can mount on a special filesystem, but also as a character device because flash memories must be managed in a special way in order to function well. It seems complex but it's not. Let's explain these concepts a bit better, and everything will be clearer.
We already introduced flash memories into Chapter 1 , Installing the Developing System, in Embedded world terms section, but what we omitted there is the fact that these devices need special management methods to work well. In fact, flash memories need the bad block detection, error detection and recovery, and a wear leveling system.
The bad block detection is a mechanism that informs the systems that a particular block of the flash is damaged and it cannot be used anymore. This is very important because for a flash device, this event is far from rare! In fact, repetitive erases and writes may damage a block, and in this scenario, when we write a new data block, we must discard it and then choose a new one. On the other hand, when we read a block, we need a way to recover the situation: this is where error detection and recovery comes in handy. The flash memory uses a supplement storage data to save some extra information that can be used later when an error occurs (in particular, NAND memories use the ECC data to do it).
Note
See more information about flashes, bad block detection, error detection and recovery, NAND flashes, and their ECC data at: https://en.wikipedia.org/wiki/Flash_memory .
Detecting an error or a bad block is not enough to correctly manage these devices. In fact, they also need a good wear leveling system, that is, a mechanism that erases (and then writes) over the whole storage area to reduce possible errors. In fact, flash memories can get damaged each time our embedded system writes or yet reads (in the case of the NAND technology) data block on them, and this probability gets higher as far as the frequency of these operations increases. That's why, the wear leveling system avoids frequent writes or reads on the same area in order to increase the lifetime of these devices.
Tip
You can get more information on the wear leveling system at: https://en.wikipedia.org/wiki/Wear_leveling .
All these aspects are to present the fact that the MTD devices (which are on top of flash devices) must implement several mechanisms to work efficiently, and that's why, in the Linux kernel, we have a dedicated devices class into the drivers/mtd
directory of the Linux repository.
In the kernel's configuration menu, we read the following:
Memory Technology Devices are flash, RAM and similar chips, often used for solid state filesystems on embedded devices.
So, MTD are used to support solid state filesystems, and they are referred by the files /dev/mtd0
, /dev/mtd1
, /dev/mtdblock0
, /dev/mtdblock1
, and so on and in a normal GNU/Linux filesystem (we'll see the differences between these two types of MTD devices soon). The MTD layer abstracts the different flash technologies to the user-level applications as shown in the following diagram:
However, it is very important to point out that these solid state filesystems are not referred to USBkeys nor to microSDs and other similar devices. This is because even if they have flash memories inside them, they are abstracted to the system as normal block devices, thanks to a Flash Translation Layer (FTL) that implements in hardware the wear leveling system, and the bad block detection and the error detection and recovery systems. Usually, USBkeys, microSDs, and other similar devices are called managed flash devices.
Tip
More information on FTL can be retrieved at: https://en.wikipedia.org/wiki/Flash_file_system#FTL .
Simply speaking, while the MTD devices need a FTL in software (in our case, implemented into the Linux kernel), the USBkeys, microSD, and so on don't need it at all since they have a FTL system inside them. These aspects are more important when we need a highly reliable system due the fact USBkeys, microSDs and so on may still have problems with wear leveling and possible corruptions at power off (the last two aspects are manufacture dependent).
Note
A good explanation about the differences between the flash devices and the usual block devices (hard disks and so on) is reported at: http://www.linux-mtd.infradead.org/faq/general.html#L_mtd_vs_hdd , where we can read that a block device has the read and write operations only while a flash device has the erase one too. Also, for the block devices, sectors are devoid of the wear-out property, while for the flash devices, the erase block operations wear out and become bad and unusable after about 103 (for MLC NAND) and 105 (NOR, SLC NAND) erase cycles.
Managing an MTD device
At this point, we have to introduce the tools needed to manage an MTD device in order to be able to put a filesystem on top of it. For this purpose, we have no choices than using the SAMA5D3 Xplained board since it's the only one that has a flash memory onboard (actually, the BeagleBone Black also has a flash device onboard, but it's an eMMC, which is a managed flash device, and it's not useful for our purposes). However, what reported below can be used on every GNU/Linux system equipped with such devices (and with the proper drivers, of course).
During the boot of the SAMA5D3 Xplained, we can notice the following kernel messages:
atmel_nand 60000000.nand: Use On Flash BBT atmel_nand 60000000.nand: Using dma0chan4 for DMA transfers. nand: device found, Manufacturer ID: 0x2c, Chip ID: 0xda nand: Micron MT29F2G08ABAEAWP nand: 256 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB si4 atmel_nand 60000000.nand: minimum ECC: 4 bits in 512 bytes atmel_nand 60000000.nand: Initialize PMECC params, cap: 4, secto2 atmel_nand 60000000.nand: Using NFC Sram read Bad block table found at page 131008, version 0x01 Bad block table found at page 130944, version 0x01 nand_read_bbt: bad block at 0x000000c80000 nand_read_bbt: bad block at 0x000000ca0000 6 ofpart partitions found on MTD device atmel_nand Creating 6 MTD partitions on "atmel_nand": 0x000000000000-0x000000040000 : "at91bootstrap" 0x000000040000-0x0000000c0000 : "bootloader" 0x0000000c0000-0x000000180000 : "bootloader env" 0x000000180000-0x000000200000 : "device tree" 0x000000200000-0x000000800000 : "kernel" 0x000000800000-0x000010000000 : "rootfs"
These messages are all referred to the flash and MTD support. In particular, we need a driver for the flash controller and one for the particular flash chips (when we use the NAND flashes, the driver is named Open NAND Flash Interface (ONFI)).
After some description messages, we must note that the MTD partitions defined in the system is as follows:
This data can be also extracted from the /proc/mtd
file in the procfs
filesystem:
root@a5d3:~# cat /proc/mtd dev: size erasesize name mtd0: 00040000 00020000 "at91bootstrap" mtd1: 00080000 00020000 "bootloader" mtd2: 000c0000 00020000 "bootloader env" mtd3: 00080000 00020000 "device tree" mtd4: 00600000 00020000 "kernel" mtd5: 0f800000 00020000 "rootfs"
Alternatively, we can take a look at the SAMA5D3 Xplained DTS file in the Linux source tree as reported in the following snippet of the arch/arm/boot/dts/sama5d3xcm.dtsi
file:
nand0: nand@60000000 { nand-bus-width = <8>; nand-ecc-mode = "hw"; atmel,has-pmecc; atmel,pmecc-cap = <4>; atmel,pmecc-sector-size = <512>; nand-on-flash-bbt; status = "okay"; at91bootstrap@0 { label = "at91bootstrap"; reg = <0x0 0x40000>; }; bootloader@40000 { label = "bootloader"; reg = <0x40000 0x80000>; }; bootloaderenv@c0000 { label = "bootloader env"; reg = <0xc0000 0xc0000>; }; dtb@180000 { label = "device tree"; reg = <0x180000 0x80000>; }; kernel@200000 { label = "kernel"; reg = <0x200000 0x600000>; }; rootfs@800000 { label = "rootfs"; reg = <0x800000 0x0f800000>; }; };
It's quite obvious that changing these settings can change SAMA5D3 Xplained's flash partitioning. This possibility is quite useful when we have to organize our mass memory due to the fact that we can pide a single chip into several logical partitions.
Note
For more information on all MTD-related concepts, you can take a look at the Linux Memory Technology Devices home page at: http://www.linux-mtd.infradead.org .
However, for our purposes, these settings are quite correct, in particular, regarding the partition labeled rootfs
where our new embedded OS will be placed.
At this point, we have to introduce the mtd-utils package where all the needed tools are placed. On our Debian OS, everything is already installed (otherwise, the aptitude
command is the solution), and using the following command, we can take a list of such tools:
root@a5d3:~# dpkg -L mtd-utils | grep bin/ | sort /usr/sbin/docfdisk /usr/sbin/doc_loadbios /usr/sbin/flashcp /usr/sbin/flash_erase /usr/sbin/flash_eraseall /usr/sbin/flash_lock /usr/sbin/flash_otp_dump /usr/sbin/flash_otp_info /usr/sbin/flash_otp_lock /usr/sbin/flash_otp_write /usr/sbin/flash_unlock /usr/sbin/ftl_check /usr/sbin/ftl_format /usr/sbin/jffs2dump /usr/sbin/jffs2reader /usr/sbin/mkfs.jffs2 /usr/sbin/mkfs.ubifs /usr/sbin/mtd_debug /usr/sbin/mtdinfo /usr/sbin/nanddump /usr/sbin/nandtest /usr/sbin/nandwrite /usr/sbin/nftldump /usr/sbin/nftl_format /usr/sbin/recv_image /usr/sbin/rfddump /usr/sbin/rfdformat /usr/sbin/serve_image /usr/sbin/sumtool /usr/sbin/ubiattach /usr/sbin/ubiblock /usr/sbin/ubicrc32 /usr/sbin/ubidetach /usr/sbin/ubiformat /usr/sbin/ubimkvol /usr/sbin/ubinfo /usr/sbin/ubinize /usr/sbin/ubirename /usr/sbin/ubirmvol /usr/sbin/ubirsvol /usr/sbin/ubiupdatevol
These commands have different usage - the ones starting with the flash
string are strictly related to the MTD device in general, the ones that start with the ubi
string are UBIFS related, and the ones that start with jffs2
are related to the JFFS2 filesystem (see the next section).
Note
These tools are not documented well (they've no man pages too!). So, the curios reader has to surf the Internet to find useful information on their usage. However, in this chapter, we will present some commands that are useful enough to start.
As the first step, let's take a look at the commands to erase and then correctly write a MTD device (remember that each flash memory, in order to be written, must be erased before). So, to erase the mtd0
device on the SAMA5D3 Xplained board, we have to use the following command:
root@a5d3:~# flash_erase /dev/mtd0 0 0 Erasing 128 Kibyte @ 20000 -- 100 % complete
Then, to write some data in the same device, we have a different way, depending on the flash technology the device is composed of. This is because these flash devices have different write modes.
On our SAMA5D3 Xplained board, we have a NAND device, so the command to use is nandwrite
. So, to write data on the just erased mtd0
device, we have to use this command:
root@a5d3:~# nandwrite -q -m -p /dev/mtd0 boot.bin
We will explain the command soon in the upcoming sections.
You can continue experiencing other commands using the --help
option argument to take the command's documentation.
Filesystems for flash memories
In a GNU/Linux system, the two major filesystems developed to manage flash memory devices are the JFFS2 and UBIFS. They are quite different from each other, but they have the same goal: implementing a good Flash Transition Layer.
JFFS2 versus UBIFS
This book cannot explain in detail all the differences between the JFFS2 and UBIFS filesystems due to the fact that these aspects are really complicated. However, we can spend some words in trying to give an idea about what these filesystems offer to the embedded developer.
First of all, we have to notice that the UBIFS filesystem, in reality, doesn't talk directly with the MTD core, but it has another layer in the middle named Unsorted Block Image (UBI) as shown in the following diagram:
In the diagram, we can also notice that while JFFS2 uses the /dev/mtdX
block devices, the UBIFS introduces the UBI volume concept. These UBI volumes are used to abstract the Physical Erase Blocks (known as PEB) of a MTD device into the Logical Erase Blocks (known as LEB) that allow a UBI volume to have the following major advantages:
- UBI volume has no bad LEBs due to the fact that the UBI layer transparency handles the bad PEBs.
- The LEBs do not wear out due to the fact that the UBI spreads the read/write/erase operations evenly across the whole flash device, implementing a transparency wear leveling system.
- UBI volumes are dynamic, and they can be created, deleted, and resized at run time.
For these reasons, using this layer, the Unsorted Block Image File System (UBIFS) can better manage the NAND flash bad blocks and provide wear leveling. However, the real advantage in using UBIFS rather than JFFS2 is that it supports the write caching and that it errs on the pessimistic side of free space calculation.
Tip
More information on UBIFS's write caching can be found at: http://www.linux-mtd.infradead.org/doc/ubifs.html#L_writeback , while information on free space calculation is located at: http://www.linux-mtd.infradead.org/faq/ubifs.html#L_df_report .
Besides these technical aspects, and simply speaking, the main advantage of UBIFS against JFFS2 is that the former scales better for larger flash memories. It also has a faster mounting, quicker access to large files, and improved write speeds. However, we still can find systems using the JFFS2 due its proven stability and a very wide usage (before the arriving of UBIFS). That's why, we present both it in this book anyway.
Tip
You can get more information on these aspects at: http://www.linux-mtd.infradead.org/doc/general.html , and especially at: http://www.linux-mtd.infradead.org/doc/jffs2.html for JFFS2 and at http://www.linux-mtd.infradead.org/doc/ubifs.html and http://www.linux-mtd.infradead.org/doc/ubi.html for UBIFS and its UBI middle layer.
Building a JFFS2 filesystem
To build a JFFS2 filesystem, we have two main ways: the first one is by doing it directly on the target board while the second one is by doing it on the host PC. Of course, the latter is the most used one since we can generate a binary image that can be written directly into the flash with a JTAG.
Tip
The JTAGs usage is not covered into this book. However, it may be important due to the fact that using it, we can easily set up an embedded system without really running it and then simplifying large systems production. For more information on these topics and how to set up a JTAG system and then use it on an embedded system, a good starting point is http://openocd.org .
Let's start by verifying that our system supports JFFS2 filesystems by looking at /proc/filesystems
as shown here:
root@a5d3:~# grep jffs2 /proc/filesystems nodev jffs2
If we get no output, it means that our system lacks the support for this filesystem and then we have to install it by recompiling the kernel as reported in Chapter 1 , Installing the Developing System, in Linux kernel for SAMA5D3 Xplained section and modifying the kernel configuration by going to File systems | Miscellaneous filesystems | Journalling Flash File System v2 (JFFS2) support. We need just to enable it as built in, leaving the other parameters at their defaults.
Now, to create a JFFS2 filesystem on the SAMA5D3 Xplained, we have first of all to erase the flash partition with the flash_erase
command. By taking a look at flash_erase
's help message, we notice that we can erase and then create a JFFS2 filesystem at the same time if we use the --jffs2
option argument, so the actual command is shown here:
Erasing 128 Kibyte @ 0 -- 0 % complete flash_erase: Cleanmarker writ ten at 0 Erasing 128 Kibyte @ 20000 -- 0 % complete flash_erase: Cleanmarker written at 20000 Erasing 128 Kibyte @ 40000 -- 0 % complete flash_erase: Cleanmarker written at 40000 Erasing 128 Kibyte @ 60000 -- 0 % complete flash_erase: Cleanmarker written at 60000 ... flash_erase: Skipping bad block at 00480000 flash_erase: Skipping bad block at 004a0000 ... flash_erase: Skipping bad block at 0f780000 flash_erase: Skipping bad block at 0f7a0000 flash_erase: Skipping bad block at 0f7c0000 flash_erase: Skipping bad block at 0f7e0000 Erasing 128 Kibyte @ f7e0000 -- 100 % complete
Note
During the execution, the command detects and signals every encountered bad blocks.
OK, that's all! Just mount the new JFFS2 partition with the usual mount
command by specifying the partition's type using the -t
option argument:
root@a5d3:~# mount -t jffs2 /dev/mtdblock5 /mnt/
Note
To erase the partition, we used the /dev/mtd5
character device, but to mount the partition, we used the /dev/mtdblock5
block device! These devices, even if of different kinds, both refer to the same physical device area. However, the character device is needed during the erasing stage because for a block device, the erasing method does not make sense, while the mount
command requires to work on a block device.
Now, in the /mnt
directory, we can write some files, and they will be stored in our NAND flash where they will remain across a complete reboot:
root@a5d3:~# mount -t jffs2 /dev/mtdblock5 /mnt/ root@a5d3:~# echo "some text" > /mnt/just_a_file root@a5d3:~# ls -l /mnt/ total 1 -rw-r--r-- 1 root root 10 Apr 2 17:44 just_a_file root@a5d3:~# umount /mnt/ root@a5d3:~# mount -t jffs2 /dev/mtdblock5 /mnt/ root@a5d3:~# cat /mnt/just_a_file some text
Tip
In the preceding example, we didn't reboot the system, but we just unmounted and then remounted the partition, which is (almost) the same.
Now, let's see how we can do the same on the host PC. This time, we need the mtd-utils package to be installed on the host PC too. So, let's install it with the usual aptitude
command and then select the JFFS2-related commands:
$ dpkg -L mtd-utils | grep jffs2 /usr/share/man/man1/mkfs.jffs2.1.gz /usr/sbin/jffs2dump /usr/sbin/jffs2reader
OK, our command is obviously mkfs.jffs2
. The command takes several options, but most of them are optional, while the only required ones (with two still optional but useful arguments within square brackets) are shown here:
mkfs.jffs2 --root=<root_filesystem> \ --pagesize=<page_size> --eraseblock=<erase_block_size> \ [--pad] [--little-endian] --output=<output_file>
By looking at the help message of mkfs.jffs2
, we can discover what the preceding options are useful for, However, regarding the optional arguments, the --pad
option can actually be omitted, but we keep it to remark that the output image must be padded until the end of the sector (this is because for a flash device, the sector must be completely rewritten and correctly filled with 0xFF). The --little-endian
option is to remark the endianness of the output file since if both --little-endian
and --big-endian
are not specified, the system will create an output file of the same endianness of the host and, in some rare cases, this may create malfunctioning (especially if we work with hosts and clients with different endianness).
Now, to execute the mkfs.jffs2
command in order to create our new JFFS2 filesystem, we need to know what is the page size and the erase block size to be put on the relative commands. We have two possible ways out: we can ask our hardware guys or we can get this information from the kernel itself. In fact, by looking at the kernel boot messages, we can get the information we need:
nand: 256 MiB, SLC, erase size: 128 KiB, page size: 2048, OOB si4
OK, now, we have just to create a dedicated directory and then create a file in it just for testing purposes:
$ mkdir mtd5_dir $ echo "some text" > mtd5_dir/just_a_file
So, the command to create our filesystem is here:
$ mkfs.jffs2 --root=mtd5_dir --pagesize=2048 --eraseblock=128 --pad --little-endian -output=mtd5.jffs2
Now, we have to move the file into our SAMA5D3 Xplained and then write it into the /dev/mtd5
flash partition:
root@a5d3:~# flash_erase /dev/mtd5 0 0 Erasing 128 Kibyte @ 460000 -- 1 % complete flash_erase: Skipping bad block at 00480000 flash_erase: Skipping bad block at 004a0000 Erasing 128 Kibyte @ f760000 -- 99 % complete flash_erase: Skipping ba d block at 0f780000 flash_erase: Skipping bad block at 0f7a0000 flash_erase: Skipping bad block at 0f7c0000 flash_erase: Skipping bad block at 0f7e0000 Erasing 128 Kibyte @ f7e0000 -- 100 % complete root@a5d3:~# nandwrite /dev/mtd5 mtd5.jffs2 Writing data to block 0 at offset 0x0
Note
First of all, don't forget to unmount the previous JFFS2 filesystem before erasing and then rewriting the data on the /dev/mtd5
partition! Then, note that this time, we didn't use the -j
option argument for the flash_erase
command as done before since now, the data we're going to write into the partition is already formatted as the JFFS2 filesystem. In the end, note also that the filesystem image is really small and it's not as large as the partition size where it is stored into. This is because the JFFS2 filesystem is capable of occupying the whole partition as soon as the system is running, and the reads/writes are performed on the flash device (this is a great feature for the system production since it dramatically reduces the setup time).
Now, we have to mount the partition and check whether the file we have created on the host PC is there:
root@a5d3:~# mount -t jffs2 /dev/mtdblock5 /mnt/ root@a5d3:~# cat /mnt/just_a_file some text
OK, our job was correct.
Building a UBIFS filesystem
Now, let's try to redo what we did in the preceding section, but with the UBIFS this time. So, erase the mtd5
partition again (after unmounting the relative mtdblock5
device!), but this time, without any special argument options:
root@a5d3:~# flash_erase /dev/mtd5 0 0
After that, we have to do a more complicated procedure to do our job. In fact, we have to format the partition as we did with hard disks, but this time, the formatting command takes the character device instead of a block one (as the fdisk
command does, for instance). The command to be used is ubiformat
. The command takes several option arguments, but this time, we just need --yes
to skip several questions where we have to answer yes in any case:
root@a5d3:~# ubiformat --yes /dev/mtd5 ubiformat: mtd5 (nand), size 260046848 bytes (248.0 MiB), 1984 eraseblocks of 1s libscan: scanning eraseblock 1983 -- 100 % complete ubiformat: 1978 eraseblocks are supposedly empty ubiformat: 6 bad eraseblocks found, numbers: 36, 37, 1980, 1981, 1982, 1983 ubiformat: formatting eraseblock 1983 -- 100 % complete
OK, now, the partition has been formatted, and we are ready to attach the mtd5
device to the UBI subsystem. This is the first real difference between UBIFS and JFFS2 (and other usual filesystems) since UBIFS requires that each partition is placed under the control of the UBI layer presented earlier. The command is ubiattach
.
Our command is the one in the first example. However, we wish to keep the number 5
for the new UBI device for better readability since we're going to use the fifth MTD device. So, the command is as follows:
root@a5d3:~# ubiattach --dev-path=/dev/mtd5 --devn=5 ubi5: attaching mtd5 ubi5: scanning is finished ubi5: attached mtd5 (name "rootfs", size 248 MiB) ubi5: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes ubi5: min./max. I/O unit sizes: 2048/2048, sub-page size 2048 ubi5: VID header offset: 2048 (aligned 2048), data offset: 4096 ubi5: good PEBs: 1978, bad PEBs: 6, corrupted PEBs: 0 ubi5: user volume: 0, internal volumes: 1, max. volumes count: 18 ubi5: max/mean erase counter: 0/0, WL threshold: 4096, image seq4 ubi5: available PEBs: 1940, total reserved PEBs: 38, PEBs reserv4 ubi5: background thread "ubi_bgt5d" started, PID 2027 UBI device number 5, total 1978 LEBs (251158528 bytes, 239.5 MiB), available 19)
Tip
The output reported after the ubiattach
command with the prefix ubi5:
is not generated by the command itself. They're kernel messages, so they are not visible if we give the command outside the serial console. If this is the case, you can read these messages with the usual dmesg
or tail -f
command. That we can use the --mtdn
option argument to specify the MTD device, just in case the character device is missing (this is useful in reduced systems where we have no automatic device nodes generation support at all as udev & Co.). In this case, the command is as follows: root@a5d3:~# ubiattach --mtdn=5 --devn=5
We can now get the UBI status using the ubinfo
command. The command usage is very simple, so using it with the --all
option argument, we can get the information needed:
root@a5d3:~# ubinfo --all UBI version: 1 Count of UBI devices: 1 UBI control device major/minor: 10:59 Present UBI devices: ubi5 ubi5 Volumes count: 0 Logical eraseblock size: 126976 bytes, 124.0 KiB Total amount of logical eraseblocks: 1978 (251158528 bytes, 239.5 MiB) Amount of available logical eraseblocks: 1940 (246333440 bytes, 234.9 MiB) Maximum count of volumes 128 Count of bad physical eraseblocks: 6 Count of reserved physical eraseblocks: 34 Current maximum erase counter value: 0 Minimum input/output unit size: 2048 bytes Character device major/minor: 249:0
Now, we need to take another step. We have to create the UBI volume related to our partition and the command to use is ubimkvol
. So, the command to do our job is as follows:
root@a5d3:~# ubimkvol /dev/ubi5 --maxavsize -N rootfs Set volume size to 246333440 Volume ID 0, size 1940 LEBs (246333440 bytes, 234.9 MiB), LEB size 126 976 bytes (124.0 KiB), dynamic, name "rootfs", alignment 1
Now, everything is in place. We just need to mount our new UBIFS partition with the usual mount
command, but with proper arguments:
root@a5d3:~# mount -t ubifs ubi5:rootfs /mnt UBIFS (ubi5:0): default file-system created UBIFS (ubi5:0): background thread "ubifs_bgt5_0" started, PID 1738 UBIFS (ubi5:0): UBIFS: mounted UBI device 5, volume 0, name "rootfs" UBIFS (ubi5:0): LEB size: 126976 bytes (124 KiB), min./max. I/O unit s izes: 2048 bytes/2048 bytes UBIFS (ubi5:0): FS size: 244682752 bytes (233 MiB, 1927 LEBs), journal size 12316672 bytes (11 MiB, 97 LEBs) UBIFS (ubi5:0): reserved for root: 4952683 bytes (4836 KiB) UBIFS (ubi5:0): media format: w4/r0 (latest is w4/r0), UUID 02B4EDD6-1 8CE-4FFF-88A4-4350C4126351, small LPT model
Note
As shown in preceding code, the messages with prefix UBIFS
came from the kernel. In the mount, we didn't specify a block device in the usual form /dev/blockdev
, but we use the volume
name instead.
OK, now, we can test the new UBIFS partition as we did earlier by creating a file in it and then verifying that it is still there across an unmount:
root@a5d3:~# echo "some text" > /mnt/just_a_file root@a5d3:~# ls /mnt/ just_a_file root@a5d3:~# umount /mnt/ UBIFS (ubi5:0): un-mount UBI device 5 UBIFS (ubi5:0): background thread "ubifs_bgt5_0" stops root@a5d3:~# ls /mnt/ root@a5d3:~# mount -t ubifs ubi5:rootfs /mnt UBIFS (ubi5:0): background thread "ubifs_bgt5_0" started, PID 1749 UBIFS (ubi5:0): UBIFS: mounted UBI device 5, volume 0, name "rootfs" UBIFS (ubi5:0): LEB size: 126976 bytes (124 KiB), min./max. I/O unit s izes: 2048 bytes/2048 bytes UBIFS (ubi5:0): FS size: 244682752 bytes (233 MiB, 1927 LEBs), journal size 12316672 bytes (11 MiB, 97 LEBs) UBIFS (ubi5:0): reserved for root: 4952683 bytes (4836 KiB) UBIFS (ubi5:0): media format: w4/r0 (latest is w4/r0), UUID 02B4EDD6-1 8CE-4FFF-88A4-4350C4126351, small LPT model root@a5d3:~# cat /mnt/just_a_file some text
Tip
For completeness, we left all kernel messages with the UBIFS prefix for documentation purposes, but you should remember that they are not displayed if the commands are executed outside the serial console.
Well, now, as for JFFS2, we will create our UBIFS partition on the host PC. We can use the mtd5_dir
directory created earlier and reshown here:
$ ls -l mtd5_dir/ total 4 -rw-rw-r-- 1 giometti giometti 10 giu 12 12:04 just_a_file $ cat mtd5_dir/just_a_file some text
This time, the command to be used is mkfs.ubifs
. As for mkfs.jffs2
, we have a lot of option arguments, but for our purposes the command to be used is quite similar:
$ mkfs.ubifs --root=mtd5_dir --min-io-size=2048 --leb-size=124KiB --max-leb-cnt=2048 --output=mtd5.ubifs
At this point, the real question is: how can we calculate the values for the option arguments --min-io-size
, --leb-size
, and --max-leb-cnt
?
Well, the answer is not easy, since we have to know a bit more in depth how UBIFS works. However, the right thing to do is just creating the UBIFS filesystem on the target machine and then get these parameters directly from the UBIFS subsystem itself! In fact, if we take a look at the earlier mount
command, we can see that the kernel tells us these values:
UBIFS (ubi5:0): background thread "ubifs_bgt5_0" started, PID 1749 UBIFS (ubi5:0): UBIFS: mounted UBI device 5, volume 0, name "rootfs" UBIFS (ubi5:0): LEB size: 126976 bytes (124 KiB), min./max. I/O unit s izes: 2048 bytes/2048 bytes UBIFS (ubi5:0): FS size: 244682752 bytes (233 MiB, 1927 LEBs), journal size 12316672 bytes (11 MiB, 97 LEBs)
For --min-io-size
and --leb-size
, the values are exposed earlier, while for --max-leb-cnt
, we have to consider that this option defines the maximum filesystem size (more strictly, the maximum UBI volume size). So, we must specify a value large enough to avoid to allocate too few LEBs for correctly mapping our mtd5
device. The last line of the preceding messages tells us that we need 1927 LEBs for user data and 97 LEBs for journaling data, so we need at least 2024 LEBs and a safe value for --max-leb-cnt
can be 2048
, which is the nearest power of 2 (for better performance).
Now, we have to create a UBI image suitable for the MTD layer where we put the UBIFS partition we just created and the command to be used is ubinize
. Before executing the command, we have to create a proper INI file useful to describe our UBI image, so in our special case, the file looks like this:
[rootfs-volume] mode=ubi image=mtd5.ubifs vol_id=5 vol_size=233MiB vol_type=dynamic vol_name=rootfs vol_flags=autoresize
The mode
parameter is currently fixed to ubi
, while image
must point to the UBIFS image created earlier. Then, the other parameters are quite obvious apart from vol_size
, vol_type
and vol_flags
, so let's explain them a bit.
The vol_type
and vol_flags
specify that the UBI volume can be dynamically allocated, and it can grow in size if the available space is present. So, in vol_size
, we can specify the minimum volume size and, when the system will attach the volume, it will increase dynamically until the maximum available size (we will verify this feature soon in this section).
OK, let's execute the ubinize
command:
$ ubinize -v --min-io-size=2048 --peb-size=128KiB --sub-page-size=2048 --output=mtd5.ubi mtd5.ini ubinize: LEB size: 126976 ubinize: PEB size: 131072 ubinize: min. I/O size: 2048 ubinize: sub-page size: 2048 ubinize: VID offset: 2048 ubinize: data offset: 4096 ubinize: UBI image sequence number: 949373716 ubinize: loaded the ini-file "mtd5.ini" ubinize: count of sections: 1 ubinize: parsing section "jffs2-volume" ubinize: mode=ubi, keep parsing ubinize: volume type: dynamic ubinize: volume ID: 5 ubinize: volume size: 251658240 bytes ubinize: volume name: rootfs ubinize: volume alignment: 1 ubinize: autoresize flags found ubinize: adding volume 5 ubinize: writing volume 5 ubinize: image file: mtd5.ubifs ubinize: writing layout volume ubinize: done
Again, we have to spend some words on the several values we have used in the ubinize
command line. The -v
option argument is just for having a verbose output. The really important parameters are --min-io-size
, which has the same meaning of the mkfs.ubifs
command, --peb-size
, which specifies the size of the physical erase blocks (note that with mkfs.ubifs
, we have specified the LEB's size instead), and --sub-page-size
, which depends on the NAND device used (but usually is equivalent to the minimum input/output unit size).
Tip
The UBIFS image is slightly bigger than the JFFS2 one even if they hold the same files:
$ ls -lh mtd5.{jffs2,ubi}
-rw-r--r-- 1 giometti giometti 128K giu 12 12:05 mt
d5.jffs2
-rw-rw-r-- 1 giometti giometti 2,0M giu 14 16:14 mt
d5.ubi
Now, as we did for JFFS2, we have to move the UBIFS image on the SAMA5D3 Xplained and then put it on the /dev/mtd5
partition. However, this time we cannot use the nandwrite
utility to write UBI data due the fact it doesn't properly format the flash partition for UBIFS. To do it we have to use the ubiformat as reported below::
root@a5d3:~# flash_erase /dev/mtd5 0 0 root@a5d3:~# ubiformat /dev/mtd5 -s 2048 -O 2048 -f mtd5.ubi
OK, now, we have to attach our UBI volume as we did earlier:
root@a5d3:~# ubiattach --dev-path=/dev/mtd5 -devn=5 ubi5: attaching mtd5 ubi5: scanning is finished gluebi (pid 1713): gluebi_resized: got update notification for unknown UBI device 5 volume 5 ubi5: volume 5 ("rootfs") re-sized from 1925 to 1940 LEBs ubi5: attached mtd5 (name "rootfs", size 248 MiB) ubi5: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes ubi5: min./max. I/O unit sizes: 2048/2048, sub-page size 2048 ubi5: VID header offset: 2048 (aligned 2048), data offset: 4096 ubi5: good PEBs: 1978, bad PEBs: 6, corrupted PEBs: 0 ubi5: user volume: 1, internal volumes: 1, max. volumes count: 128 ubi5: max/mean erase counter: 1/0, WL threshold: 4096, image sequence number: 1394936512 ubi5: available PEBs: 0, total reserved PEBs: 1978, PEBs reserved for bad PEB handling: 34 ubi5: background thread "ubi_bgt5d" started, PID 1717 UBI device number 5, total 1978 LEBs (251158528 bytes, 239.5 MiB), ava ilable 0 LEBs (0 bytes), LEB size 126976 bytes (124.0 KiB)
Note
As said earlier, at the attach time, the system discovers that it can enlarge the volume from 1925 to 1940 LEBs, that is, the maximum available space, and then, it proceeds to carry on the operation:
gluebi (pid 1713): gluebi_resized: got update notif ication for unknown UBI device 5 volume 5 ubi5: volume 5 ("rootfs") re-sized from 1925 to 194 0 LEBs ubi5: attached mtd5 (name "rootfs", size 248 MiB)
Note also that the messages with the ubi5:
prefix are kernel messages.
Great! Now, the last step is to mount the partition and then verify that all data is in place:
root@a5d3:~# mount -t ubifs ubi5:rootfs /mnt UBIFS (ubi5:5): background thread "ubifs_bgt5_5" started, PID 174 UBIFS (ubi5:5): UBIFS: mounted UBI device 5, volume 5, name "roo" UBIFS (ubi5:5): LEB size: 126976 bytes (124 KiB), min./max. I/O s UBIFS (ubi5:5): FS size: 244936704 bytes (233 MiB, 1929 LEBs), j) UBIFS (ubi5:5): reserved for root: 0 bytes (0 KiB) UBIFS (ubi5:5): media format: w4/r0 (latest is w4/r0), UUID 8A2Bl root@a5d3:~# ls /mnt/ just_a_file root@a5d3:~# cat /mnt/just_a_file some text
Tip
Note that the messages with the USBIFS
prefix are kernel messages.
OK, everything is good.