Porting Ubuntu Snappy to a yet unsupported armhf board

With the appearance of Snappy Ubuntu steps into the world of embedded systems. Ubuntu Snappy is designed in a way that will make it safe to run in critical environments from drones over medical equipment to robotics, home automation and machine control. The automatic rollback features will prevent you from outages when an upgrade fails, application confinement prevents you from apps, servers and tools doing any evil to your system and the image based design makes upgrades happen in minutes instead of potentially hours you are used to from package based upgrade systems.

By its design of separating device, rootfs and application packages strictly Snappy provides a true rolling release, you just upgrade each of the bits separately, independent from each other. Your Home Automation Server software can just stay on the latest upstream version all the time, no matter what version or release the other bits of your system are on. There is no more “I’m running Ubuntu XX.04 or XX.10, where do i find a PPA with a backport of the latest LibreOffice”, “snappy install” and “snappy upgrade” will simply always get you the latest stable upstream version of your software, regardless of the base system.

Thanks to the separation of the device related bits porting to yet unsupported hardware is a breeze too, though since features like automated roll-back on upgrades as well as the security guarding of snap packages depend on capabilities of the bootloader and kernel, your port might operate slightly degraded until you are able to add these bits.

Let’s take a look what it takes to do such a port to a NinjaSphere developer board in detail.

The Snappy boot process and finding out about your Bootloader capabilities

This section requires some basic u-boot knowledge, you should also have read https://developer.ubuntu.com/en/snappy/porting/

By default the whole u-boot logic in a snappy system gets read and executed from a file called snappy-system.txt living in the /boot partition of your install. This file is put in place by the image build software we will use later. So first of all your Bootloader setup needs to be able to load files from disk and read their content into the bootloader environment. Most u-boot installs provide “fatload” and the “env import” commands for this.

It is also very likely that the commands in your snappy-system.txt are to new for your installed u-boot (or are simply not enabled in its build configuration) so we might need to override them with equivalent functions your bootloader actually supports (i.e. fatload vs load or bootm vs bootz).

To get started, we grab a default linux SD card image from the board vendor, write it to an SD card and wire up the board for serial console using an FTDI USB serial cable. We stop the boot process by hitting enter right after the first u-boot messages appear during boot, which should get us to the bootloader prompt where we simply type “help”. This will show us all the commands the installed bootloader knows. Next we want to know what the bootloader does by default, so we call the “printenv” command which will show us all pre-set variables (copy paste them from your terminal application to a txt file so you can easier look them up later without having to boot your board each time you need to know anything).

Inspecting the “printenv” output of the NinjaSphere u-boot you will notice that it uses a file called uEnv-NS.txt to read its environment from. This is the file we will have to work with to put overrides and hardware sepcific bits in place. It is also the file from which we will load snappy-system.txt into our environment.

Now lets take a look at the snappy-system.txt file, an example can be found at:
http://people.canonical.com/~ogra/snappy/snappy-system.txt

It contains four variables we can not change that tell our snappy how to boot, these are snappy_cmdline, snappy_ab, snappy_stamp and snappy_mode. It also puts the logic for booting a snappy system into the snappy_boot variable.
Additionally there are the different load commands for kernel, initrd and devicetree files and as you can see when comparing these with your u-boot “help” output they use commands out installed u-boot does not know, so the first bits we will put into our uEnv-NS.txt files are adjusted version of these commands. In the default instructions for the NinjaSphere for building the Kernel you will notice that it uses the devicetree attached to an uImage and can not boot raw vmlinuz and initrd.img files by using the bootz command. It also does not use an initrd at all by default but luckily in the “printenv” output there is at least a load address set for a ramdisk already, so we will make use of this. Based on these findings our first lines in uEnv-NS.txt look like the following:


loadfiles_ninja=run loadkernel_ninja; run loadinitrd_ninja
loadkernel_ninja=fatload mmc ${mmcdev} ${kloadaddr} ${snappy_ab}/${kernel_file_ninja}
loadinitrd_ninja=fatload mmc ${mmcdev} ${rdaddr} ${snappy_ab}/${initrd_file_ninja}
kernel_file_ninja=uImage
initrd_file_ninja=uInitrd

We will now simply be able to run “loadfiles_ninja” instead of “loadfiles” from our snappy_boot override command.

Snappy uses ext4 filesystems all over the place, looking at “printenv” we see the NinjaSphere defaults to ext3 by setting the mmcrootfstype variable, so our next line in uEnv-NS.txt switches this to ext4:

mmcrootfstype=ext4

Now lets take a closer look at snappy_boot in snappy-system.txt, the command that contains all the magic.
The section  “Bootloader requirements for Snappy (u-boot + system-AB)” on https://developer.ubuntu.com/en/snappy/porting/ describes the if-then logic used there in detail. Comparing the snappy_boot command from snappy-system.txt with the list of available commands shows that we need some adjustments though, the “load” command is not supported, we need to use “fatload” instead. The original snappy_boot command also uses “fatwrite” to touch snappy-stamp.txt. While you can see from the “help” output, that this command is supported by our preinstalled u-boot, there is a bug with older u-boot versions where using fatwrite results in a corrupted /boot partition if this partition is formatted as fat32 (which snappy uses). So our new snappy_boot command will need to have this part of the logic ripped out (which sadly breaks the auto-rollback function but will not have any other limitations for us (“snappy upgrade” will still work fine as well as a manual “snappy rollback” will)).

After making all the changes our “snappy_boot_ninja” will look like the following in the uEnv-NS.txt file:


snappy_boot_ninja=if test "${snappy_mode}" = "try"; then if fatload mmc ${mmcdev} ${snappy_stamp} 0; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; fi; fi; run loadfiles_ninja; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootm ${kloadaddr} ${rdaddr}

As the final step we now just need to set “uenvcmd” to import the variables from snappy-system.txt and then make it run our modified snappy_boot_ninja command:


uenvcmd=fatload mmc ${mmcdev} ${loadaddr} snappy-system.txt; env import -t $loadaddr $filesize; run snappy_boot_ninja

This is it ! Our bootloader setup is now ready, the final uEnv-NS.txt that we will put into our /boot partition now looks like below:


# hardware specific overrides for the ninjasphere developer board
#
loadfiles_ninja=run loadkernel_ninja; run loadinitrd_ninja
loadkernel_ninja=fatload mmc ${mmcdev} ${kloadaddr} ${snappy_ab}/${kernel_file_ninja}
loadinitrd_ninja=fatload mmc ${mmcdev} ${rdaddr} ${snappy_ab}/${initrd_file_ninja}


kernel_file_ninja=uImage
initrd_file_ninja=uInitrd
mmcrootfstype=ext4


snappy_boot_ninja=if test "${snappy_mode}" = "try"; then if fatload mmc ${mmcdev} ${snappy_stamp} 0; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; fi; fi; run loadfiles_ninja; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootm ${kloadaddr} ${rdaddr}


uenvcmd=fatload mmc ${mmcdev} ${loadaddr} snappy-system.txt; env import -t $loadaddr $filesize; run snappy_boot_ninja

Building kernel and initrd files to boot Snappy on the NinjaSphere

Snappy makes heavy use of the apparmor security extension of the linux kernel to provide a safe execution environment for the snap packages of applications and services. So while we could now clone the NinjaSphere kernel source and apply the latest apparmor patches from linus’ mainline tree, the kind Paolo Pisati from the Ubuntu kernel team was luckily interested in getting the NinjaSphere running snappy and did all this work for us already, so instead of cloning the BSP kernel from the NinjaSphere team on github, we can pull the already patched tree from:

http://kernel.ubuntu.com/git?p=ppisati/ubuntu-vivid.git;a=shortlog;h=refs/heads/snappy_ti_ninjasphere

First of all, let us install a cross toolchain. Assuming you use an Ubuntu or Debian install for your work you can just do this by:


sudo apt-get install gcc-arm-linux-gnueabihf

Now we clone the patched tree and move into the cloned directory:


git clone -b snappy_ti_ninjasphere git://kernel.ubuntu.com/ppisati/ubuntu-vivid.git
cd ubuntu-vivid

Build uImage with attached devicetree, build the modules and install them. All based on Paolos adjusted snappy defconfiig:


export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm
make snappy_ninjasphere_defconfig
make -j8 uImage.var-som-am33-ninja
make -j8 modules
mkdir ../ninjasphere-modules
make modules_install INSTALL_MOD_PATH=../ninjasphere-modules
cp arch/arm/boot/uImage.var-som-am33-ninja ../uImage
cd -

So we now have a modules/ directory containing the binary modules and we have a uImage file to boot our snappy, what we are still missing is an initrd file to make our snappy boot. We can just use the initrd from an existing snappy device tarball which we can find at cdimage.ubuntu.com.


mkdir tmp
cd tmp
wget http://cdimage.ubuntu.com/ubuntu-core/daily-preinstalled/current/vivid-preinstalled-core-armhf.device.tar.gz
tar xzvf vivid-preinstalled-core-armhf.device.tar.gz

Do you remember, our board requires an uInitrd … the above tarball only ships a raw initrd.img, so we need to convert it. In Ubuntu there is the u-boot-tools package that ships the mkimage tool to convert files for u-boot consumption, lets install this package and create a proper uInitrd:


sudo apt-get install u-boot-tools
mkimage -A arm -T ramdisk -C none -n "Snappy Initrd" -d system/boot/initrd.img-* ../uInitrd
cd ..
rm -rf tmp/

If you do not want to keep the modules from the -generic kernel in your initrd.img you can easily unpack and re-pack the initrd.img file as described in “Initrd requirements for Snappy” on https://developer.ubuntu.com/en/snappy/porting/ and simply rm -rf lib/modules/* before re-packing to get a clean and lean initrd.img before converting to uInitrd.

Now we have a bootloader configuration file, uImage, uInitrd and a dir with the matching binary modules we can use to create our snappy device tarball.

Creating the Snappy device tarball

We are ready to create the device tarball filesystem structure and roll a proper snappy tarball from it, lets create a build/ dir in which we build this structure:


mkdir build
cd build

As described on https://developer.ubuntu.com/en/snappy/porting/ our uInitrd and uImage files need to go into the assets subdir:


mkdir assets
cp ../uImage assets/
cp ../uInitrd assets/

The modules we built above will have to live underneath the system/ dir inside the tarball:


mkdir system
cp -a ../modules/* system/

Our boootloader configuration goes into the boot/ dir. For proper operation snappy looks for a plain uEnv.txt file, since our actual bootloader config lives in uEnv-NS.txt we just create the other file as empty doc (it would be great if we could use a symlink here, but remember, the /boot partition that will be created from this uses a vfat filesystem and vfat does not support
symlinks, so we just touch an empty file instead).


mkdir boot
cp ../uEnv-NS.txt boot/
touch boot/uEnv.txt

Snappy will also expect a flashtool-assets dir, even though we do not use this for our port:


mkdir flashtool-assets

As last step we now need to create the hardware.yaml file as described on https://developer.ubuntu.com/en/snappy/porting/


echo "kernel: assets/uImage" >hardware.yaml
echo "initrd: assets/uInitrd" >>hardware.yaml
echo "dtbs: assets/dtbs" >>hardware.yaml
echo "partition-layout: system-AB" >>hardware.yaml
echo "bootloader: u-boot" >>hardware.yaml

This is it ! Now we can tar up the contents of the build/ dir into a tar.xz file that we can use with ubuntu-device-flash to build a bootable snappy image.


tar cJvf ../device_part_ninjasphere.tar.xz *
cd ..

Since I personally like to re-build my tarballs regulary if anything changed or improved, I wrote a little tool I call snappy-device-builder which takes over some of the repetitive tasks you have to do when rolling the tarball, you can branch it with bzr from launchpad if you are interested in it (patches and improvements are indeed very welcome):


bzr branch lp:~ogra/+junk/snappy-device-builder

Building the actual SD card image

Install the latest ubuntu-device-flash from the snappy-dev beta PPA:


sudo add-apt-repository ppa:snappy-dev/beta
sudo apt-get update
sudo apt-get install ubuntu-device-flash

Now we build a 3GB big image called mysnappy.img using ubuntu-device-flash and our newly created device_part_ninjasphere.tar.xz with the command below:


sudo ubuntu-device-flash core --size 3 -o mysnappy.img --channel ubuntu-core/devel-proposed --device generic_armhf --device-part device_part_ninjasphere.tar.xz --developer-mode

.. and write the create mysnappy.img to an SD card that sits in the SD Card Reader at /dev/sdc:


sudo dd if=mysnappy.img of=/dev/sdc bs=4k

This is it, your NinjaSphere board should now boot you to a snappy login on the serial port, log in with “ubuntu” with the password “ubuntu” and if your board is attached to the network i recommend doing a “sudo snappy install webdm”, then you can reach your snappy via http://webdm.local:4200/ in a browser and install/remove/configure snap packages on it.

If you have any problems with this guide, want to make suggestions or have questions, you can reach me as “ogra” via IRC inthe #snappy channel on irc.freenode.net or just mail the snappy-devel@lists.ubuntu.com mailing list with your question.

2 comments

  1. Pingback: Developer Preview: Docker & Snappy
  2. Xilihutu Yuan · September 12, 2015

    This is actually cool!!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s