When we started doing gadget snap packages for UbuntuCore images, there was no snapcraft. Gadgets were assembled from locally built bootloader binaries by setting up a filesystem structure that reflects the snap content, using pre-created meta/snap.yaml and meta/gadget.yaml files and then calling mksquashfs.
When snapcraft started to support the gadget format we added a very simple snapcraft.yaml that simply used the dump plugin to copy the prebuilt binaries into place in the resulting snap.
While we provide uboot.patch files in the gadget source trees, there is not really anything built from source at snap build time and doing your own modifications means you need to reach out to someone who has the necessary knowledge how the u-boot.img and the SPL were built. This was a long standing wart in our setup and there was desire for a long time to actually make the gadget creation a completely reproducable process based on upstream u-boot sources.
A typical build process would look like:
– git checkout git://git.denx.de/u-boot.git
– switch to the right release branch
– apply the uboot.patch to the tree
– run make $config_of_your_board
– run make (… and if you cross build, set the required environment up first)
After this the resulting binaries used to be copied into the prebuilt/ dir. The snapcraft build was completely disconnected from this process.
Auto-building u-boot from source with snapcraft
Snapcraft is well able to define all of these steps in the snapcraft.yaml nowadays, actually build a useful binary for us and put it in the right place in the final snap. So lets go step by step through creating a working “part:” entry for the snapcraft.yaml that provides the above steps:
parts: uboot: plugin: make source: git://git.denx.de/u-boot.git source-branch: v2017.01 artifacts: [MLO, u-boot.img]
We use the “make” plugin (which nicely provides the “artifacts:” option for us to cherry-pick the binaries from the u-boot build to be put into the snap), point to the upstream u-boot source and make it use the v2017.01 branch.
prepare: | git apply ../../../uboot.patch make am335x_boneblack_config
With this “prepare:” scriptlet we tell the plugin to apply our uboot.patch to the checked out branch and to configure it for a beaglebone black before starting the build.
install: | tools/mkenvimage -r -s 131072 -o $SNAPCRAFT_PART_INSTALL/uboot.env ../../../uboot.env.in cd $SNAPCRAFT_PART_INSTALL/; ln -s uboot.env uboot.conf
If you have worked with u-boot gadgets before you know how important the uboot.env file that carries our UbuntuCore bootloader setup is. It always needs to be the right size (-s 131072), redundant (-r) to allow atomic writing and we ship the input file as uboot.env.in in our source trees. In the “install:” scriptlet we take this input file and create a proper environment image file from it using the mkenvimage tool our build has just created before. The ubuntu-image and “snap prepare-image” commands will look for an “uboot.conf” file at image creation time, so we create a symlink pointing to our binary env file.
build-packages: - libpython2.7-dev - build-essential - bc
Dependencies to build u-boot get defined in the “build-packages:” option of the part. Obviously we need a compiler (build-essential), some build scripts still use python2.7 headers (libpython2.7-dev) and when test building there is a complaint about bc missing that is not fatal (but disturbing enough to also install the bc package as a build dependency).
After adding a bit of the general meta data like name, version, summary and description as well as all the snap informational data like type, (target) architecture, confinement type and stability grade, the resulting snapcraft.yaml looks like:
name: bbb version: 16-0.1 summary: Beagle Bone Black description: | Bootloader files and partitoning data to create a bootable Ubuntu Core image for the Beaglebone Black. type: gadget architectures: - armhf confinement: strict grade: stable parts: uboot: plugin: make source: git://git.denx.de/u-boot.git source-branch: v2017.01 artifacts: [MLO, u-boot.img] prepare: | git apply ../../../uboot.patch make am335x_boneblack_config install: | tools/mkenvimage -r -s 131072 -o $SNAPCRAFT_PART_INSTALL/uboot.env ../../../uboot.env.in cd $SNAPCRAFT_PART_INSTALL/; ln -s uboot.env uboot.conf build-packages: - libpython2.7-dev - build-essential - bc
This snapcraft.yaml is enough to build a beaglebone gadget snap natively on an armhf host, so it will work if you run “snapcraft” in the checked out source on a raspberry Pi install or if you let launchpad or build.snapcraft.io do the build for you … but … typically, while developing you want to build on your workstation PC, not on some remote source or on a slow arm board. With some modifications of the snapcraft.yaml we can luckily make that possible very easily, lets make a copy of our snapcraft.yaml (i call it crossbuild-snapcraft.yaml in my trees) and add some changes to that.
Allow cross Building
First of all, we want a cross compiler on the host machine, so we will add the gcc-arm-linux-gnueabi package to the list of build dependencies.
build-packages: - libpython2.7-dev - build-essential - bc - gcc-arm-linux-gnueabi
We also need to override the “make” call to carry info about our cross compiler in the CROSS_COMPILE environment variable. We can use a “build:” scriptlet for this.
build: | CROSS_COMPILE=arm-linux-gnueabi- make
When cross building, the “artifacts:” line sadly does not do what it should anymore (i assume this is a bug), as a quick workaround we can enhance the “install:” script snippet with a simple cp command.
install: | cp MLO u-boot.img $SNAPCRAFT_PART_INSTALL/ tools/mkenvimage -r -s 131072 -o $SNAPCRAFT_PART_INSTALL/uboot.env ../../../uboot.env.in cd $SNAPCRAFT_PART_INSTALL/; ln -s uboot.env uboot.conf
With all these changes in place our crossbuild-snapcraft.yaml now looks like:
name: bbb version: 16-0.1 summary: Beagle Bone Black description: | Bootloader files and partitoning data to create a bootable Ubuntu Core image for the Beaglebone Black. type: gadget architectures: - armhf confinement: strict grade: stable parts: uboot: plugin: make source: git://git.denx.de/u-boot.git source-branch: v2017.01 artifacts: [MLO, u-boot.img] prepare: | git apply ../../../uboot.patch make am335x_boneblack_config build: | CROSS_COMPILE=arm-linux-gnueabi- make install: | cp MLO u-boot.img $SNAPCRAFT_PART_INSTALL/ tools/mkenvimage -r -s 131072 -o $SNAPCRAFT_PART_INSTALL/uboot.env ../../../uboot.env.in cd $SNAPCRAFT_PART_INSTALL/; ln -s uboot.env uboot.conf build-packages: - libpython2.7-dev - build-essential - bc - gcc-arm-linux-gnueabi
So with the original snapcraft.yaml we can now let our tree auto-build in build.snapcraft.io, when we checkout the source locally and want to build on a PC a simple “cp crossbuild-snapcraft.yaml snapcraft.yaml && snapcraft” will do a local cross build.
Creating the gadget.yaml
Just building the bootloader binaries is indeed not enough to create a bootable image, the binaries need to go in the right place, the bootloader will need to know where the devicetree file can be found and a working image should also have a proper partition table. For this purpose we will need to create a gadget.yaml file with the right information.
We create a gadget.yaml file in the source tree and tell the system that the devicetree file is called am335x-boneblack and that it gets shipped by the kernel snap.
device-tree: am335x-boneblack device-tree-origin: kernel
Now we add a “volume:” entry that tells the system about the bootloader type (grub or u-boot) and defines which type of partition table we want (either “gpt” for a GUID partiton table or “mbr” for an msdos type one).
volumes: disk: bootloader: u-boot schema: mbr
(Note that in newer versions of the ubuntu-image tool the –ouput option to give your image a meaningful name has been deprecated, instead the name of the volume from the gadget snap is used now. To give your image a more meaningful name you might want to change “disk:” above to something else like “beagleboneblack:” to get a beagleboneblack.img file)
The last bit we need to do is to give our volume a “structure:”, i.e. a partition table but also info where to write the raw bootloader bits (MLO and u-boot.img).
Looking a the elinux wiki [3] how to create a bootable SD card for the beaglebone black we find lines like:
dd if=MLO of=/dev/sdX count=1 seek=1 conv=notrunc bs=128k
dd if=u-boot.img of=/dev/sdX count=2 seek=1 conv=notrunc bs=384k
For writing the bootloader blobs into the right place ubuntu-image will not just use dd so we need to translate the lines into proper entries for the volume structure. Lets take a closer look. The MLO line tells us dd will use 128k (131072 bytes) sized blocks (bs=), it will use 1 block offset from the start of the card (seek=1) and will reserve one block for the MLO payload in use (count=1). and indeed there is no filesystem in use, it will be written “bare”.
This gives us the first entry in the volume structure.
structure: - name: mlo type: bare size: 131072 offset: 131072 content: - image: MLO
The u-boot.img dd command uses a block size of 384k (393216 bytes), one block offset from the start of the image and reserves two blocks as possible size for the u-boot.img binary and will also write the binary raw into place (type: bare).
- name: u-boot type: bare size: 786432 offset: 393216 content: - image: u-boot.img
Currently every UbuntuCore u-boot image expects to find the bootloader configuration, kernel, initrd and devicetree file in a vfat partition (type: 0C) called system-boot. To have enough wiggle room we’ll make that partition 128M big that will leave enough space for even gigantic kernel binaries or initrd’s. The ubuntu-image tool will put our uboot.env file from the start into that partition.
- name: system-boot type: 0C filesystem: vfat filesystem-label: system-boot size: 128M
The final gadget.yaml file will now look like:
device-tree: am335x-boneblack device-tree-origin: kernel volumes: disk: bootloader: u-boot schema: mbr structure: - name: mlo type: bare size: 131072 offset: 131072 content: - image: MLO - name: u-boot type: bare size: 786432 offset: 393216 content: - image: u-boot.img - name: system-boot type: 0C filesystem: vfat filesystem-label: system-boot size: 128M
As you can see building a gadget snap is fairly easy and only requires four files (snapcraft.yaml, gadget.yaml, uboot.patch and uboot.env.in) to have in a github tree that you can then have auto-built on build.snapcraft.io. In subsequent posts i will explain the patch and uboot.env.in files in more detail. I will also describe the setup of default interfaces a gadget can provide as well as how to set some system defaults from the gadget.yaml file. If you want to take a look at the full source tree used for the above example, go to [1].
Documentation of the gadget snap syntax can be found at [2]. The dd commands used as input for the gadget.yaml file can be found at [3] and documentation how to build an image out of a gadget snap is at [4]. If you have any questions feel free to ask at [5] (i recommmend using the “device” category).
[1] https://github.com/ogra1/beaglebone-gadget
[2] https://forum.snapcraft.io/t/the-gadget-snap/696
[3] http://elinux.org/Beagleboard:U-boot_partitioning_layout_2.0
[4] https://docs.ubuntu.com/core/en/guides/build-device/image-building
[5] https://forum.snapcraft.io/
6 comments