Working on U-boot from Yocto (iMX8)

Working on U-boot from Yocto (iMX8)

I have been asked a couple of times how to quickly make changes for U-Boot in Yocto. Those who asked have used to rebuild and flash an entire image each time, which takes an unnecessarily which is not a fast procedure.

To rebuild U-Boot is`nt significantly different from any other recipe, but one difference is that imx-mkimage must also be built to generate a bootable image.

I wrote this post because it is just easier for me to write it down and refer rather than keep repeat myself :-)

So, this is my workflow when working with U-boot for iMX8 in an Yocto environment.

Tools I use

bitbake

All working with Yocto will somehow use bitbake. No further explanation.

devtool

devtool [1] is a command line tool that provides a number of features that help you build, test and package software. It is incredible useful when working with Yocto and making changes to the software. It will let you work with the software, make changes, commit and then smoothly create patches and include them into the recipe.

kas

kas [2] is my absolute favorite tool to administrate and setup bitbake based projects.

I always use kas-container which is part of the projects. kas-container does the very same thing as kas but execute every command in a container. This is useful if you, for example, using a rolling distribution (Archlinux in my case) where the tools is not always compatible with the latest software on your system.

The following steps will assume that you use kas-container, but can easely be adapted to whatever you use.

kas-container and menuconfig

There is one thing that you must be aware of though - the Dockerfile [3] used by kas-container does not include all dependencies to run menuconfig. So if you want to make changes to the configuration file using menuconfig then you need to add them somehow.

I do always clone the repository [4] myself and add the missing dependencies (bison and flex):

[08:55:11]marcus@goliat:~/git/kas$ git diff
diff --git a/Dockerfile b/Dockerfile
index 0e79cb5..b5b3866 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,7 +33,7 @@ RUN apt-get update && \
	python3-pip python3-setuptools python3-wheel python3-yaml python3-distro python3-jsonschema \
	python3-newt python3-colorlog python3-kconfiglib \
	gosu lsb-release file vim less procps tree tar bzip2 zstd pigz lz4 unzip tmux libncurses-dev \
-        git-lfs mercurial iproute2 ssh-client telnet curl rsync gnupg awscli sudo \
+        git-lfs mercurial iproute2 ssh-client telnet curl rsync gnupg awscli sudo bison flex \
	socat bash-completion && \
 apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Another thing to keep in mind; Yocto does not have a menuconfig option for U-boot. In other words, you cannot use devtool menuconfig u-boot-imx in order to open up menuconfig. You have to run make menuconfig by yourself in the working directory.

This is no problem if you run just kas though.

The workflow

Start a development shell in the container:

kas-container shell ./my-kas-file.yml

Use devtool to create a workspace for the u-boot-imx repository:

devtool modify u-boot-imx
cd /build/workspace/sources/u-boot-imx

You will find the workspace in /build/workspace/sources/u-boot-imx.

Now you can make changes to the source code as you like and even commit your changes.

#make changes to the repository
git commit -a --signoff -m "my cool changes"

Usually you just run devtool build <package> and eventually devtool deploy <package> to test it on target. However, this does not work for u-boot (for at least iMX8) as the output needs to be run though the ìmx-mkimage tool before is is usable.

Also, devtool build-image does not work either as it will only include the newly built u-boot into the image without run it through imx-mkimage.

So, what we have to do is generate patches and add them into the recipe. devtool will help us with that:.

devtool update-recipe u-boot-imx -a /work/layers/meta-custom

-a /work/layers/meta-custom, tells devtool to update that specific layer, otherwise it will update meta-freescale, and that is something we want to avoid.

kas-container maps the working directory to /work/, you will probably find your layers there.

The last thing we need to do is to build the imx-boot target that is provided by the imx-mkimage recipe. imx-boot will also rebuild u-boot-imx with our patches applied.

To do this we use bitbake:

bitbake imx-boot

That is all.

The generate imx-boot image you find in build/tmp/deploy/images/<machine>/imx-boot is ready to be flashed to your hardware using the uuu [5] tool.

Summary

Working with U-boot is not more complicated to work on than anything else, but you should be aware of that you cannot use devtool as usual as the image u-boot-imx produce is not the final product that can be flashed to the hardware.

Also, there is no good ways to invoke the complete build chain with only devtool, so bitbake has to be used for that.

Skip flashing unused blocks with UUU

Skip flashing unused blocks with UUU

TL;DR: UUU does now (or will shortly) support blockmaps for flashing images. Use it. It will shorten your flashing time *a lot.*

It will soon be time to manufacture circuit boards for a project I'm currently working on. After manufacturing, it will need some firmware for sure, but how do we flash it in the most efficient way? The board is based on an i.MX.8 chip from NXP and has an eMMC as storage medium.

Overall, short iteration is nice to have as it will benifit the development cycles for the developers on a daily basis, but the time it takes to flash a device with its firmware is not just "nice to have" in the production line, it's critical. We do not want to have a (another?) bottleneck in the production line, so we have to make some effort to make it as fast as possible.

The Scenario

The scenario we have in front of us is that:

  • We want to write a full disk image to the eMMC in the production
  • Most space in the disk image is reserved for empty partitions
  • The disk image is quite big (~12GB)
  • Only ~2GB is of the disk image is "real data" (not just chunks of zeros)
  • Flash firmware to eMMC takes ~25min which is unacceptable

Sparse files is to me a very familiar concept and the .wic file created by the Yocto project is indeed a sparse file. But the flashing tool (UUU) did not seems to support write sparse images without expanding them (that is not completely true, as we will see).

To speed up the production line, I thought of several different solutions including:

  • Do not include empty partitions in the .wic file, create those at the first boot instead
  • Only use UUU to boot the board and then flash the disk image using dfu-utils
  • ...

But all have its own drawbacks. Best would be if we could stick to a uniform way of flashing everything we need, and UUU will be needed regardless.

Before we go any further, lets look at what a sparse file is.

Sparse images

A sparse image [2] is a type of file that contains metadata to describe empty sections (let's call them holes) instead of filling it up with zeros (that take up actual space). The advantage is that sparse files only take as much space as they actually need.

For example; If the logical file size is, say 128 GiB, and the areas with real data is only 8GiB, the physical file size will only be 8GiB+metadata. The allocated size are significantly reduced compared to a full 128GiB image.

/media/sparse-file.png

Most of the filesystems in Linux (btrfs, XFS, ext[234], ...) and even Windows (NTFS) handle sparse files transparently to the user. This means that the filesystem will not allocate more memory than necessary. Reading these holes returns just zeros, and the filesystem will allocate blocks on the filesystem only when someone actually writes to those holes.

Think on it as the Copy-On-Write (COW) feature that many modern filesystems implements.

An example

It could be a little bit confusing when trying to figure out the file size for sparse files.

For example, ls reports 14088667136 bytes, which is quite a big file!

$ ls -alrt image.wic
-rw-r--r-- 1 marcus marcus 14088667136 12 dec 11.08 image.wic

But du only reports 1997888 bytes?!

$ du  image.wic
1997888    image.wic

To get the real occupied size, we need to print the allocated size in blocks (-s):

$ ls -alrts image.wic
1997888 -rw-r--r-- 1 marcus marcus 14088667136 12 dec 11.08 image.wic

If you want to see it with your own eyes, you can easily create a 2GB file with the truncate command:

$ truncate  -s  2G image.sparse
$ stat image.sparse 
  File: image.sparse
    Size: 2147483648	Blocks: 0          IO Block: 4096   regular file

As you can see, the file size is 2GB, but it occupies zero blocks on the disk. The file does only consists of a big chunk of zeros which is described in the metadata.

Handle the holes with care

It's easy to expand the sparse files if you are not careful. The most common applications that is used to copy files have a --sparse option to preserve the holes. If not used, it will allocate blocks and fill them out with zeros. For some applications the --sparse option is set as their default behavior, e.g., see the relevant part of the manpage for cp(1):

--sparse=WHEN
              control creation of sparse files. See below

 ...
 By default, sparse SOURCE files are detected by a crude heuristic and the corresponding DEST file is made sparse as well.
 That  is  the  behavior  selected  by --sparse=auto.
 Specify --sparse=always to create a sparse DEST file whenever the SOURCE file contains a long enough sequence of zero bytes.
 Use --sparse=never to inhibit creation of sparse files.

While some applications does not use it as default, e.g. rsync(1):

--sparse, -S
        Try to handle sparse files efficiently so they take up less space on the destination.  If combined with --inplace the file created might not end  up  with
        sparse  blocks  with some combinations of kernel version and/or filesystem type.  If --whole-file is in effect (e.g. for a local copy) then it will always
        work because rsync truncates the file prior to writing out the updated version.

        Note that versions of rsync older than 3.1.3 will reject the combination of --sparse and --inplace.

All kind of compression/decompression of files usually expands sparse files as well.

bmap-tools

bmap-tools [4] is a handy tool for creating block maps (bmaps) for a file and then use that information and copy that file to a media in a more efficient way.

The advantages of bmap-tools compared to e.g. dd is (as they state on their Github page) :

  • Faster. Depending on various factors, like write speed, image size, how full is the image, and so on, bmaptool was 5-7 times faster than dd in the Tizen IVI project.
  • Integrity. bmaptool verifies data integrity while flashing, which means that possible data corruptions will be noticed immediately.
  • Usability. bmaptool can read images directly from the remote server, so users do not have to download images and save them locally.
  • Protects user's data. Unlike dd, if you make a mistake and specify a wrong block device name, bmaptool will less likely destroy your data because it has protection mechanisms which, for example, prevent bmaptool from writing to a mounted block device.

The tool comes with two sub commands, create and copy. The create subcommand generates the block maps and copy commands copy a file to a certain destinatin.

A typical usage of dd could be to write an image (bigimage) to to a SD-card ( /dev/sdX):

 $ bzcat bigimage | dd of=/dev/sdX bs=1M conv=fsync

The dd command will copy all data, including those zeroes for the empty partitions. Instead of copy all data with dd, we could generate a block map file with metadata that describes all the "empty" sections:

bmaptool create bigimage > bigimage.bmap

The bmap file is a human readable XML file that shows all the block maps and also checksums for each block:

<?xml version="1.0" ?>
<!-- This file contains the block map for an image file, which is basically
     a list of useful (mapped) block numbers in the image file. In other words,
     it lists only those blocks which contain data (boot sector, partition
     table, file-system metadata, files, directories, extents, etc). These
     blocks have to be copied to the target device. The other blocks do not
     contain any useful data and do not have to be copied to the target
     device.

     The block map an optimization which allows to copy or flash the image to
     the image quicker than copying of flashing the entire image. This is
     because with bmap less data is copied: <MappedBlocksCount> blocks instead
     of <BlocksCount> blocks.

     Besides the machine-readable data, this file contains useful commentaries
     which contain human-readable information like image size, percentage of
     mapped data, etc.

     The 'version' attribute is the block map file format version in the
     'major.minor' format. The version major number is increased whenever an
     incompatible block map format change is made. The minor number changes
     in case of minor backward-compatible changes. -->

<bmap version="2.0">
    <!-- Image size in bytes: 13.1 GiB -->
    <ImageSize> 14088667136 </ImageSize>

    <!-- Size of a block in bytes -->
    <BlockSize> 4096 </BlockSize>

    <!-- Count of blocks in the image file -->
    <BlocksCount> 3439616 </BlocksCount>

    <!-- Count of mapped blocks: 1.9 GiB or 14.5%     -->
    <MappedBlocksCount> 499471  </MappedBlocksCount>

    <!-- Type of checksum used in this file -->
    <ChecksumType> sha256 </ChecksumType>

    <!-- The checksum of this bmap file. When it's calculated, the value of
         the checksum has be zero (all ASCII "0" symbols).  -->
    <BmapFileChecksum> 19337986514b3952866af5fb80054c40166f608f42efbf0551956e80678aba46 </BmapFileChecksum>

    <!-- The block map which consists of elements which may either be a
         range of blocks or a single block. The 'chksum' attribute
         (if present) is the checksum of this blocks range. -->
    <BlockMap>
        <Range chksum="a53e6dcf984243a7d87a23fed5d28f3594c72a83081e90e296694256cfc26395"> 0-2 </Range>
        <Range chksum="a60a80a89aa832b3480da91fe0c75e77e24b29c1de0fcdffb9d2b50102013ff5"> 8-452 </Range>
        <Range chksum="7b07df777cd3441ac3053c2c39be52baef308e4befabd500d0c72ef4ab7c5565"> 2048-12079 </Range>
        <Range chksum="cf6ac0e4aec163a228bad0ab85722e011dd785fc8d047dc4d0f86e886fa6684d"> 24576-24902 </Range>
        <Range chksum="7521285590601370cc063cc807237eaf666f879d84d4fcae001026a7bb5a7eff"> 24904-24905 </Range>
        <Range chksum="9661e72b75fe483d53d10585bff79316ea38f15c48547b99da3e0c8b38634ceb"> 24920-26173 </Range>
        <Range chksum="93d69200bd59286ebf66ba57ae7108cc46ef81b3514b1d45864d4200cf4c4182"> 40824-57345 </Range>
        <Range chksum="56ed03479c8d200325e5ef6c1c88d8f86ef243bf11394c6a27faa8f1a98ab30e"> 57656-122881 </Range>
        <Range chksum="cad5eeff8c937438ced0cba539d22be29831b0dae986abdfd3edc8ecf841afdb"> 123192-188417 </Range>
        <Range chksum="88992bab7f6a4d06b55998e74e8a4635271592b801d642c1165528d5e22f23ff"> 188728-253953 </Range>
        <Range chksum="f5332ff218a38bf58e25b39fefc8e00f5f95e01928d0a5edefa29340f4a24b42"> 254264-319489 </Range>
        <Range chksum="714c8373aa5f7b55bd01d280cb182327f407c434e1dcc89f04f9d4c522c24522"> 319800-509008 </Range>
        <Range chksum="b1ec6c807ea2f47e0d937ac0032693a927029ff5d451183a5028b8af59fb3dd2"> 548864 </Range>
        <Range chksum="554448825ffae96d000e4b965ef2c53ae305a6734d1756c72cd68d6de7d2a8b0"> 548867 </Range>
        <Range chksum="f627ca4c2c322f15db26152df306bd4f983f0146409b81a4341b9b340c365a16"> 660672-660696 </Range>
        <Range chksum="fa01901c7f34cdb08467000c3b52a4927e208a6c128892018c0f5039fedb93c0"> 661504-661569 </Range>
        <Range chksum="ee3b76f97d2b039924e60726a165f688c93c87add940880a5d6a4a62fe4f7876"> 661573 </Range>
        <Range chksum="db3aa8c65438e58fc0140b81da36ec37c36fa94549b43ad32d36e206a9ec7819"> 661577-661578 </Range>
        <Range chksum="726f800d4405552f2406fb07e158a3966a89006f9bf474104c822b425aed0f18"> 662593-662596 </Range>
        <Range chksum="e9085dd345859b72dbe86b9c33ce44957dbe742ceb5221330ad87d83676af6cb"> 663552-663553 </Range>
        <Range chksum="1f3660636b045d5fc31af89f6e276850921d34e3e008a2f5337f1a1372b61114"> 667648-667649 </Range>
        <Range chksum="b3f3b8a0818aeade05b5e7af366663130fdc502c63a24a236bb54545d548d458"> 671744-671745 </Range>
        <Range chksum="b4a759d879bc4a47e8b292a5a02565d551350e9387227016d8aed62f3a668a1d"> 675840-675841 </Range>
        <Range chksum="1ff00c7f6feca0b42a957812832b89e7839c6754ca268b06b3c28ba6124c36fd"> 679936-679937 </Range>
        <Range chksum="9bbedd9ac9ab58f06abc2cdc678ac5ddf77c117f0004139504d2aaa321c7475f"> 694272 </Range>
        <Range chksum="c8defb770c630fa1faca3d8c2dff774f7967a0b9902992f313fc8984a9bb43a8"> 696320-698368 </Range>
        <Range chksum="9d5fad916ab4a1f7311397aa3f820ae29ffff4c3937b43a49a0a2a62bf8d6846"> 712704-712705 </Range>
        <Range chksum="48f179784918cf9bf366201edc1585a243f892e6133aa296609486734398be7f"> 716800-716801 </Range>
        <Range chksum="cc0dd36d4891ae52c5626759b21edc4894c76ba145da549f85316646f00e8f2d"> 727040 </Range>
        <Range chksum="955be1419c8df86dc56151737426c53f5ba9df523f66d2b6056d3b2028e5240d"> 759808 </Range>
        <Range chksum="ab6cafa862c5a250b731e41e373ad49767535f6920297376835f04a9e81de023"> 759811 </Range>
        <Range chksum="0ec8e7c7512276ad5fabe76591aeda7ce6c3e031b170454f800a00b330eea2de"> 761856-761857 </Range>
        <Range chksum="de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31"> 789488-789503 </Range>
        <Range chksum="8c8853e6be07575b48d3202ba3e9bb4035b025cd7b546db7172e82f97bd53b1c"> 790527-791555 </Range>
        <Range chksum="918fc7f7a7300f41474636bf6ac4e7bf18e706a341f5ee75420625b85c66c47e"> 791571 </Range>
        <Range chksum="c25cb25875f56f2832b2fcd7417b7970c882cc73ffd6e8eaa7b9b09cbf19f172"> 791587 </Range>
        <Range chksum="52aec994d40f31ce57d57391a8f6db6c23393dfca6d53b99fb8f0ca4faad0253"> 807971-807976 </Range>
        <Range chksum="8ffcde1a642eb739a05bde4d74c73430d5fb4bcffe4293fed7b35e3ce9fe16df"> 823296-823298 </Range>
        <Range chksum="1d21f93952243d3a6ba80e34edcac9a005ad12c82dec79203d9ae6e0c2416043"> 888832-888834 </Range>
        <Range chksum="cdb117909634c406611acef34bedc52de88e8e8102446b3924538041f36a6af9"> 954368-954370 </Range>
        <Range chksum="18945d38f9ac981a16fb5c828b42da4ce86c43fe7b033b7c69b21835c4a3945b"> 1019904-1019906 </Range>
        <Range chksum="1ad3cc0ed83dad0ea03b9fcc4841332361ded18b8e29c382bfbb142a962aef81"> 1085440-1085442 </Range>
        <Range chksum="c19fc180da533935f50551627bd3913d56ff61a3ecf6740fbc4be184c5385bc9"> 1314816 </Range>
        <Range chksum="b045e6499699f9dea08702e15448f5da3e4736f297bbe5d91bb899668d488d22"> 1609728-1609730 </Range>
        <Range chksum="a25229feee84521831f652f3ebcbb23b86192c181ef938ed0af258bfde434945"> 1675264-1675266 </Range>
        <Range chksum="741a9ded7aee9d4812966c66df87632d3e1fde97c68708a10c5f71b9d76b0adf"> 1839104-1839105 </Range>
        <Range chksum="d17c50d57cfb768c3308eff3ccc11cf220ae41fd52fe94de9c36a465408dd268"> 1871872-1888255 </Range>
        <Range chksum="c19fc180da533935f50551627bd3913d56ff61a3ecf6740fbc4be184c5385bc9"> 2363392 </Range>
        <Range chksum="1747cefe9731aa9a0a9e07303199f766beecaef8fd3898d42075f833950a3dd2"> 2396160-2396162 </Range>
        <Range chksum="c19fc180da533935f50551627bd3913d56ff61a3ecf6740fbc4be184c5385bc9"> 2887680 </Range>
        <Range chksum="ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7"> 2887695 </Range>
        <Range chksum="de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31"> 3411952-3411967 </Range>
        <Range chksum="7d501c17f812edb240475aa0f1ee9e15c4c8619df82860b2bd008c20bb58dc6d"> 3414000 </Range>
        <Range chksum="f71e4b72a8f3d4d8d4ef3c045e4a35963b2f5c0e61c9a31d3035c75a834b502c"> 3414016-3414080 </Range>
        <Range chksum="98033bc2b57b7220f131ca9a5daa6172d87ca91ff3831a7e91b153329bf52eb1"> 3414082-3414084 </Range>
        <Range chksum="b5a2b3aaf6dcfba16895e906a5d98d6167408337a3b259013fa3057bafe80918"> 3414087 </Range>
        <Range chksum="81b766d472722dbc326e921a436e16c0d7ad2c061761b2baeb3f6723665997c5"> 3414886-3414890 </Range>
        <Range chksum="2250b13e06cc186d3a6457a16824a3c9686088a1c0866ee4ebe8e36dfe3e17d7"> 3416064 </Range>
        <Range chksum="a82ec47f112bff20b91d748aebc769b2c9003b7eb5851991337586115f31da62"> 3420160 </Range>
        <Range chksum="99dcc3517a44145d057b63675b939baa7faf6b450591ebfccd2269a203cab194"> 3424256 </Range>
        <Range chksum="f5a94286ab129fb41eea23bf4195ca85e425148b151140dfa323405e6c34b01c"> 3426304-3427328 </Range>
        <Range chksum="d261d607d8361715c880e288ccb5b6602af50ce99e13362e8c97bce7425749d2"> 3428352 </Range>
        <Range chksum="d6ff528a483c3a6c581588000d925f38573e7a8fd84f6bb656b19e61cb36bd06"> 3432448 </Range>
        <Range chksum="de2f256064a0af797747c2b97505dc0b9f3df0de4f489eac731c23ae9ca9cc31"> 3439600-3439615 </Range>
    </BlockMap>
</bmap>

Once the bmap file is generated, we can copy the file to , e.g. our SD-card (/dev/sdX) using the copy subcommand:

bmaptool copy bigimage /dev/sdX

In my case, this was 6 times faster compared to dd.

Yocto

Yocto supports OpenEmbedded Kickstart Reference (.wks) to describe how to create a disk image.

A .wks file could look like this:

part u-boot --source rawcopy --sourceparams="file="${IMAGE_BOOTLOADER}   --no-table    --align ${IMX_BOOT_SEEK}
part /boot  --source bootimg-partition                        --fstype=vfat --label boot --active --align 8192 --size 64
part        --source rootfs                                   --fstype=ext4 --label root    --align 8192 --size 1G
part                                                          --fstype=ext4 --label overlay --align 8192 --size 500M
part /mnt/data                                                --fstype=ext4 --label data --align 8192 --size 10G
bootloader --ptable msdos

The format is rather self explained. Pay some attention to the --size parameters. Here we create several (empty) partitions which is actually quite big.

The resulting image occupy 14088667136 bytes in total, but only 1997888 bytes is non-sparse data.

Yocto is able to generate a block map file using the bmaptool for the resulting ẁic` file. Just specify that you want to generate a wic.bmap image among your other fs types:

IMAGE_FSTYPES = "wic.gz wic.bmap"

UUU

The Universal Update Utility (UUU) [1] is a image deploy tool for Freescale/NXP I.MX chips. It allows downloading and executing code via the Serial Download Protocol (SDP) when booted into manufacturing mode.

The tool let you flash all of most the common types of storage devices such as NAND, NOR, eMMC and SD-cards.

That is good, but the USB-OTG port that is used for data transfer is only USB 2.0 (Hi-Speed) which limit the transfer speed to 480Mb/s.

A rather common case is to write a full disk image to a storage device using the tool. A full disk image contains, as you may guessed, a full disk, this includes all unpartionated data, partition table, all partitions, both those that contains data and eventually any empty partitions. In other words, if you have a 128MiB storage, your disk image will occupy 128MiB, and 128MiB of data will be downloaded via SDP.

Support for sparse files could be handy here.

UUU claims to support sparse images by its raw2sparse type, but that is simply a lie, the code that detects continous chunks of data is actually commented out [3] for unknown reason:

//int type = is_same_value(data, pheader->blk_sz) ? CHUNK_TYPE_FILL : CHUNK_TYPE_RAW;
int type = CHUNK_TYPE_RAW;

Pending pull request

Luckily enough, there is a pull request [5] that add support for using bmap files with UUU!

The PR is currently (2023-12-11) not merged but I hope is is soon. I've tested it out and it works like a charm.

EDIT: The PR was merged 2023-12-12, one day after I wrote this blog entry :-)

With this PR you can provide a bmap file to the flash command via the -bmap <image> option in your .uuu script. E.g. :

uuu_version 1.2.39

SDPS: boot -f ../imx-boot

FB: ucmd setenv emmc_dev 2
FB: ucmd setenv mmcdev ${emmc_dev}
FB: ucmd mmc dev ${emmc_dev}
FB: flash -raw2sparse -bmap rawimage.wic.bmap all rawimage.wic
FB: done

It cut the time flashing down to a sixth!

As this is a rather new feature in the UUU tool, I would like to promote it and thank dnbazhenov @Github for implement this.

kas-container and QEMU

kas-container and QEMU

KAS

KAS [1] is a setup tool for bitbake based projects such as Yocto. There are many similiar alternatives out there and I've tried most of them, but my absolute favorite is KAS.

In order to use KAS, you have to setup a YAML file to contain information about your machine, distribution, meta layers and local configuration. Here is a small example configuration copied from the KAS documentation:

# Every file needs to contain a header, that provides kas with information
# about the context of this file.
header:
  # The `version` entry in the header describes for which configuration
  # format version this file was created for. It's used by kas to figure
  # out if it's compatible with this file. The version is an integer that
  # is increased on every format change.
  version: x
# The machine as it's written into the `local.conf` of bitbake.
machine: qemux86-64
# The distro name as it's written into the `local.conf` of bitbake.
distro: poky
repos:
  # This entry includes the repository where the config file is located
  # to the bblayers.conf:
  meta-custom:
  # Here we include a list of layers from the poky repository to the
  # bblayers.conf:
  poky:
    url: "https://git.yoctoproject.org/git/poky"
    commit: 89e6c98d92887913cadf06b2adb97f26cde4849b
    layers:
      meta:
      meta-poky:
      meta-yocto-bsp:

bblayers_conf_header:
  meta-custom: |
    POKY_BBLAYERS_CONF_VERSION = "2"
    BBPATH = "${TOPDIR}"
    BBFILES ?= ""    
local_conf_header:
  meta-custom: |
    PATCHRESOLVE = "noop"
    CONF_VERSION = "1"
    IMAGE_FSTYPES = "tar"    

That is all you need to start to build your distribution:

kas build kas-project.yml

kas-container

KAS also comes with kas-container. It does the same thng and takes the same arguments as the kas command, but it executes in a container (either docker or podman) instead.

For people like me who use ArchLinux (or other rolling distributions), building in a container is preferred as you otherwise will end up with wierd incompatibility problems pretty soon.

It's also useful on e.g. build servers as those tend to have an unpredictable environment as well.

Yocto and QEMU

Yocto [3] let you emulate and virtualize your images you have built using the Yocto Project. It makes use of the runqemu help script to find the build artifacts and setup everything to start the emulator.

To add support for qemu you have to include the qemuboot image class into your local.conf:

IMAGE_CLASSES += "qemuboot"

When you include the image class to your project it will generate an *.qemuboot.conf file among your artifacts and contains the configuration for runqemu.

The configuration file [4] has many variables that can be overridden in your local.conf. Here are some of them:

  • QB_SYSTEM_NAME - qemu name, e.g., "qemu-system-i386"
  • QB_OPT_APPEND - options to append to qemu, e.g., "-device usb-mouse"
  • QB_DEFAULT_KERNEL - default kernel to boot
  • QB_DEFAULT_FSTYPE - default FSTYPE to boot
  • QB_MEM - memory
  • QB_MACHINE - qemu machine
  • QB_CPU - qemu cpu
  • QB_SMP - amount of CPU cores inside qemu guest, each mapped to a thread on the host
  • QB_KERNEL_CMDLINE_APPEND - options to append to kernel's -append option
  • QB_DTB - qemu dtb name
  • QB_AUDIO_DRV - qemu audio driver
  • QB_AUDIO_OPT - qemu audio option
  • QB_RNG - Pass-through for host random number generator
  • QB_KERNEL_ROOT - kernel's root gets passed to the kernel.
  • QB_NETWORK_DEVICE - network device
  • QB_TAP_OPT - network option for 'tap' mode
  • QB_SLIRP_OPT - network option for SLIRP mode
  • QB_CMDLINE_IP_SLIRP - If QB_NETWORK_DEVICE adds more than one network interface to qemu
  • QB_ROOTFS_OPT - used as rootfs
  • QB_SERIAL_OPT - serial port
  • QB_TCPSERIAL_OPT - tcp serial port option
  • QB_ROOTFS_EXTRA_OPT - extra options to be appended to the rootfs device in case there is none specified by QB_ROOTFS_OPT.
  • QB_GRAPHICS - QEMU video card type
  • QB_NFSROOTFS_EXTRA_OPT - extra options to be appended to the nfs rootfs options in kernel boot arg

QB_MEM is set to -m 256 as default. I had to increase it a lot as I'm running Azure in my setup.

kas-container and QEMU

Running qemu in kas-container is pretty straight forward, there is just a few things to keep in mind if you need a network connection.

By default, qemu is mapping a TAP [5] interface to the emulated environment to be able to route traffic to your network. First, it requires you to have the tap module loaded on the host:

sudo modprobe tap

runqemu then uses iptables to setup NAT routing for the TAP interface. Unfortunately, iptables is not included in the docker image so we have to add it. You find the Dockerfile in the KAS repository [6].

diff --git a/Dockerfile b/Dockerfile
index 0e79cb5..c331e45 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,7 +33,7 @@ RUN apt-get update && \
         python3-pip python3-setuptools python3-wheel python3-yaml python3-distro python3-jsonschema \
         python3-newt python3-colorlog python3-kconfiglib \
         gosu lsb-release file vim less procps tree tar bzip2 zstd pigz lz4 unzip tmux libncurses-dev \
-        git-lfs mercurial iproute2 ssh-client telnet curl rsync gnupg awscli sudo \
+        git-lfs mercurial iproute2 ssh-client telnet curl rsync gnupg awscli sudo iptables \
         socat bash-completion && \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
diff --git a/kas-container b/kas-container
index 8fa2d16..2cd88e1 100755
--- a/kas-container
+++ b/kas-container
@@ -135,7 +135,7 @@ run_clean() {
        fi
 }

Now we have to expose the tap device into the container and give it permission to create network rules. This is done by send --device /dev/net/tun:/dev/net/tun and --cap-add=NET_ADMIN as arguments to docker.

BE AWARE OF THAT YOU NOW ARE GIVING THE CONTAINER NET_ADMIN CAPABILITIES!

Now we are ready to start a shell:

kas-container --docker-args "--device /dev/net/tun:/dev/net/tun --cap-add=NET_ADMIN"  shell ./kas-project.yml
/media/kas-container-qemu.png

As we do not export a framebuffer device to the container, we start runqemu with the nographic parameter.

runqemu nographic

[  OK  ] Finished IPv6 Packet Filtering Framework.
[  OK  ] Finished IPv4 Packet Filtering Framework.
[  OK  ] Reached target Preparation for Network.
         Starting Network Configuration...
[  OK  ] Finished OpenSSH Key Generation.
[  OK  ] Started D-Bus System Message Bus.
[  OK  ] Started User Login Management.
[  OK  ] Started Network Configuration.
         Starting Wait for Network to be Configured...
         Starting Network Name Resolution...
[  OK  ] Started Network Name Resolution.
[  OK  ] Reached target Network.
[  OK  ] Reached target Host and Network Name Lookups.
         Starting containerd container runtime...
         Starting DNS forwarder and DHCP server...
         Starting Hostapd IEEE 802.…A2/EAP/RADIUS Authenticator...
[  OK  ] Started DNS forwarder and DHCP server.
[  OK  ] Started containerd container runtime.

Test Distro v1 qemuarm64 ttyAMA0

qemuarm64 login:

Here we go.

meta-readonly-rootfs-overlay

meta-readonly-rootfs-overlay

meta-readonly-rootfs-overlay [1] is a meta layer for the Yocto project [2] originally written by Claudius Heine. I took over the maintainership in May 2022 to keep it updated with recent Yocto releases and keep add functionality.

I've implemented it in a couple of industrial products so far and think it needs some extra attention as I find it so useful.

Why does this exists?

Having a read-only root file system is useful for many scenarios:

  • Separate user specific changes from system configuration, and being able to find differences
  • Allow factory reset, by deleting the user specific changes
  • Have a fallback image in case the user specific changes made the root file system no longer bootable.

Because some data on the root file system changes on first boot or while the system is running, just mounting the complete root file system as read-only breaks many applications. There are different solutions to this problem:

  • Symlinking/Bind mounting files and directories that could potentially change while the system is running to a writable partition
  • Instead of having a read-only root files system, mounting a writable overlay root file system, that uses a read-only file system as its base and writes changed data to another writable partition.

To implement the first solution, the developer needs to analyse which file needs to change and then create symlinks for them. When doing factory reset, the developer needs to overwrite every file that is linked with the factory configuration, to avoid dangling symlinks/binds. While this is more work on the developer side, it might increase the security, because only files that are symlinked/bind-mounted can be changed.

This meta-layer provides the second solution. Here no investigation of writable files are needed and factory reset can be done by just deleting all files or formatting the writable volume.

How does it work?

The implementation make use of OverlayFS [3], which is a union mount filesystem that combines multiple underlying mount points into one. The filesystem make use of the terms upper and lower filesystem where the upper is filesystem is applied as an overlay on the lower filesystem.

The resulting merge directory is a combination of these two where all files in the upper filesystem overrides all files in the lower.

/media/meta-readonly-rootfs-overlay.png

Dependencies

This layer only depends on:

URI: git://git.openembedded.org/bitbake
branch: kirkstone

and

URI: git://git.openembedded.org/openembedded-core
layers: meta
branch: kirkstone

Usage

Adding the readonly-rootfs-overlay layer to your build

In order to use this layer, you need to make the build system aware of it.

Assuming the readonly-rootfs-overlay layer exists at the top-level of your OpenEmbedded source tree, you can add it to the build system by adding the location of the readonly-rootfs-overlay layer to bblayers.conf, along with any other layers needed. e.g.:

BBLAYERS ?= " \
  /path/to/layers/meta \
  /path/to/layers/meta-poky \
  /path/to/layers/meta-yocto-bsp \
  /path/to/layers/meta-readonly-rootfs-overlay \
  "

To add the script to your image, just add:

IMAGE_INSTALL:append = " initscripts-readonly-rootfs-overlay"

to your local.conf or image recipe. Or use core-image-rorootfs-overlay-initramfs as initrd.

Read-only root filesystem

If you use this layer you do not need to set read-only-rootfs in the IMAGE_FEATURES or EXTRA_IMAGE_FEATURES variable.

Kernel command line parameters

These examples are not meant to be complete. They just contain parameters that are used by the initscript of this repository. Some additional paramters might be necessary.

Example using initrd

root=/dev/sda1 rootrw=/dev/sda2

This cmd line start /sbin/init with the /dev/sda1 partition as the read-only rootfs and the /dev/sda2 partition as the read-write persistent state.

root=/dev/sda1 rootrw=/dev/sda2 init=/bin/sh

The same as before but it now starts /bin/sh instead of /sbin/init.

Example without initrd

root=/dev/sda1 rootrw=/dev/sda2 init=/init

This cmd line starts /sbin/init with /dev/sda1 partition as the read-only rootfs and the /dev/sda2 partition as the read-write persistent state. When using this init script without an initrd, init=/init has to be set.

root=/dev/sda1 rootrw=/dev/sda2 init=/init rootinit=/bin/sh

The same as before but it now starts /bin/sh instead of /sbin/init

Details

All kernel parameters that is used to configure meta-readonly-rootfs-overlay:

  • root - specifies the read-only root file system device. If this is not specified, the current rootfs is used.
  • `rootfstype if support for the read-only file system is not build into the kernel, you can specify the required module name here. It will also be used in the mount command.
  • rootoptions specifies the mount options of the read-only file system. Defaults to noatime,nodiratime.
  • rootinit if the init parameter was used to specify this init script, rootinit can be used to overwrite the default (/sbin/init).
  • rootrw specifies the read-write file system device. If this is not specified, tmpfs is used.
  • rootrwfstype if support for the read-write file system is not build into the kernel, you can specify the required module name here. It will also be used in the mount command.
  • rootrwoptions specifies the mount options of the read-write file system. Defaults to rw,noatime,mode=755.
  • rootrwreset set to yes if you want to delete all the files in the read-write file system prior to building the overlay root files system.