Skip to content

Instantly share code, notes, and snippets.

@sergiusens
Last active August 27, 2017 06:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sergiusens/fa40a344ad4b395a99ed to your computer and use it in GitHub Desktop.
Save sergiusens/fa40a344ad4b395a99ed to your computer and use it in GitHub Desktop.

Snappy for Devices porting guide

This guide will get you started on porting snappy to your device. It covers setting the tools on your development machine and walks you through the basics of snappy porting by looking at the runtime requirement of a snappy system from a partition layout perspective. It will then go in depth explaining what is needed to make a snappy bootloader, kernel and initrd and will touch briefly on the requirements for installing/flashing images using ubuntu-device-flash.

Getting started

First of all, you should make sure you have enabled the Snappy Tools PPA, as explained in the snappy introduction.

To get started, please download the device tarball for one of the supported architecures build used for the 15.04 release. Run the folliwing to obtain the url to download from,

ubuntu-device-flash query --device generic_arm64 --show-image --channel ubuntu-core/15.04/edge | grep device-

Change generic_arm64 accordingly.

You will need to create an oem snap, some bases for this can be found in http://bazaar.launchpad.net/~snappy-dev/snappy-hub/snappy-systems/files

Once an oem snap is created for the target and the device tarball is available, an image can be created by running,

sudo ubuntu-device-flash core 15.04 -o my-snappy.img \
  --channel edge --oem <oem.snap> \
  --enable-ssh --device-part=./device.tar.xz

Let's break that down into smaller chunks, so we can see more easily what's going on:

sudo ubuntu-device-flash core 15.04 \  # create an ubuntu core image for 15.04 series
  -o my-snappy.img \                   # create your snappy.img
  --channel edge \                     # use the latest build from 15.04 series (see Channels)
  --oem <oem.snap> \                   # oem enablement for the device to port to
  --enable-ssh \                       # enable ssh for convenience
  --device-part=./device.tar.xz        # make use of the local device part

This will create a snappy Ubuntu Core image for the target board that uses the local enablement bits provided in the ./device.tar.xz we have downloaded above.

To flash the my-snappy.img we produced to an SD card, simply use the dd command:

# NOTE: replace /dev/sdX with the the block device of your SD card, 
# maybe it's also something like /dev/mmcblk0
sudo dd if=my-snappy.img of=/dev/sdX bs=32M
sync

Snappy enablement basics & partitions

The image we just created has four partitions, which map directly to the required partitions to support the snappy system image runtime.

Number  Start    End      Size     File system  Name          Flags
1       ##MiB    ##MiB    ##MiB    fat32        system-boot
2       ##MiB    ##MiB    ##MiB    ext4         system-a
3       ##MiB    ##MiB    ##MiB    ext4         system-b
4       ##MiB    ##MiB    ##MiB    ext4         writable

It’s worth noticing that those partitions have the appropriate label and that the kernel can resolve them through that label as standard block devices. Besides that, snappy Ubuntu Core has no hard requirements on exactly what storage medium you put these on your device.

However, to keep things simple we recommend to stick to a pure SD card layout, which as a side effect will make it easy to reuse our standard flashing tool (ubuntu-device-flash) without much effort.

Let’s look at the partitions and how their content is composed by ubuntu-device-flash.

The system-boot partition

The system-boot partition is where all the boot essential artifacts are stored. On snappy systems that use the u-boot bootloader, this is a FAT32 partition that is mounted to /boot/uboot on a booted snappy device. The size of this partition is not specified, but system builders should pick a comfortable size that easily fits 3 kernels and 3 initrds in - with buffer. It contains a few top level artifacts as well as two directories called a/ and b/ that contain the enablement bits needed by the bootloader to boot into ubuntu systems installed in the system-a and system-b labeled partitions.

In general a a/ or b/ file will contain

  • /boot/uboot/a/dtbs/am335x-boneblack.dtb, the device tree binary for the platform
  • /boot/uboot/a/vmlinuz, the snappy enabled kernel
  • /boot/uboot/a/initrd.img, the snappy enabled initrd

To define which files to put in place here you can configure in the hardware.yaml file that is inside the device tarball. While not strictly needed for the snappy runtime behaviour, the hardware.yaml file that was used to install the boot essential enablement bits is kept for reference in the matching boot directory.

Every device tar has a hardware.yaml that looks like:

kernel: assets/vmlinuz        # where in device tarball is the kernel?
initrd: assets/initrd.img     # where in device tarball is the initrd?
dtbs: assets/dtbs             # where in device tarball are the dtbs available

Once you do an initial upgrade of your snappy system you will find the same layout described above in the /boot/uboot/b directory. The files just match the ones needed to boot into snappy Ubuntu system installed on the system-b partition. If you step one level up in the file system, you will see that there are still files that are neither associated with a/ nor with b/:

$ find  /boot/uboot/ -maxdepth 1 -type f
/boot/uboot/uEnv.txt
/boot/uboot/snappy-system.txt

The system-a and system-b partitions

To realize the transactional paradigm of system updates, snappy supports a system-ab partition scheme that allows to atomically update and rollback the system running. For that to work, the snappy tool requirements those partitions to be labelled system-a and system-b. They need to be of same size (recommended: 1GB) which must fit the ubuntu rootfs. For products these partition currently must use the ext4 filesystem, but as long as your bootloader can resolve the partitions by label and your kernel and initrd can fsck and mount the that partition you are free to experiment with whatever filesystem you’re interested in. Let us know if you plan to go for production with anything but ext4 so we can discuss if and how to support such vision.

Besides those facts, the the system-a and system-b partitions are pretty boring from an enablers point of view as there is not much to do for that role. The majority of content in there is a 1:1 copy of the device independent Ubuntu image (aka ubuntu-core). The only bits that the device enablers contributes to system-a/-b partitions are kernel modules that are not boot essential and can be loaded on demand after the system partition is mounted. Your device tarball has to ship those in the system/lib/modules/KERNEL-VERSION directory that matches the version of the kernel shipped for that system.

In our device tarball you downloaded in the beginning you can observe that we ship something similar to a system/lib/modules/3.16.0-28-generic folder that ships all the typical peripheral support one expects from a fully working Ubuntu system.

IMPORTANT: The device enabler must not try to use the system/ directory to ship any other files than kernel modules residing in the directory above as we are likely to break all potential uses except that of shipping kernel modules in that location as part of the upcoming snappy milestones. If you feel the need to ship something beyond this, please discuss your requirement on the snappy-devel mailing list.

The writable partition

The writable partition is not that interesting for this porting guide. Again for products we require this partition to be an ext4 formatted Linux partition, but nothing speaks against you picking another filesystem if you don’t plan to ship a snappy powered product with it. Since the “writable” partition is used not only for user data, but also for installing snapps we recommend to size this partition generously to deliver a nice and extensible device experience to your users.

The writable partition will be mounted to /writable and maps to various places of the standard LFS to enable snappy system config mechanism to work. Since this is not relevant for enablers we won’t look into the mount mounts used, but you can use the mount command on any snappy system if you are interested to learn more about the places we use the writable partition.

Runtime vs. Flashtime

Since we realize that for products the flashing approach can vary, this guide is explicitly focussed on specifying the runtime requirements of the partitioning scheme used. However, we believe that for development boards providing a straightforward and standardized way to flash builds to a standard block device such as SD card or usb disk that then can be directly booted on your platform can greatly improve the convenience and productivity that developers experience.. If you want to provide a development platform, we highly recommend you to make your platform so that images for your platform can be produced using our ubuntu-device-flashing tool and then dd’ed to a block device and then simply plugged into your development board for booting snappy.

If you need help how to realize that vision as part of your BSP roadmap, please don’t hesitate to reach out to us on snappy-devel mailing list or through your official partner contact in Canonical. Snappy enablement basics and the device tarball.

In this section we want to take a look at enablement from the perspective of the device tarball, which is the main artifact for system enablers to bundle and distribute their development BSPs for snappy.

All the runtime relevant elements in here have already been explained in the section “Snappy enablement basics & partitions” above; hence only a quick reference explaining how the individual pieces get mapped to the snappy system partitions:

  • hardware.yaml: config file referencing runtime assets used by ubuntu-device-flash.
  • assets: directory shipping the boot essential assets of a snappy BSP (kernel, initrd and device tree binary where supported.
  • system: directory shipping kernel modules that are only needed after the rootfs has been mounted; you must not use this directory for anything but shipping ABI compatible kernel modules matching your kernel referenced in the hardware.yaml and shipped in the assets directory

If you want to use ubuntu-device-flash to also support creating convenient block devices images that just boot for your platform you might be happy to check out the flash.yaml file which gives you some facilities to customize the behaviour for your needs. flash.yaml is known to be in very early stages, so if you have input on how to make ubuntu-device-flash useful for your specific case to allow producing block devices that can be used booted, please reach out to snappy development team on the [mailto:snappy-devel@lists.ubuntu.com](mailing list).

Bootloader requirements for Snappy (u-boot + system-AB)

“System A/B” is a Snappy feature to have two sets of root filesystems and boot files (typically kernel, initrd and optionally fdt/device tree), hence “A/B”. When an update breaks the system preventing it from boot to a healthy state, snappy will automatically fall back to the previously use system. Read this section to achieve this setup with Snappy and U-Boot.

Modern U-Boot configs on many hacker boards will read an uEnv.txt file on the first partition formatted as FAT and merge the values in the U-Boot environment. This can be extended by adding a snappy-system.txt file which is under Snappy’s control and implements the A/B boot logic. The U-Boot defaults for your board or your uEnv.txt file need to define some variables that the Snappy logic relies on (which are described further below), and then call into the Snappy boot procedure.

Snappy installation provides a snappy-system.txt which is under Snappy’s control and defines snappy_boot. snappy_boot is an U-Boot variable defining a procedure which will implement the A/B selection and fallback. It’s behavior is described in this pseudo-code:

if trying to boot a new version (snappy_mode=try); then

    if snappy stamp file (snappy-stamp.txt) exists; then
        # previous boot attempt failed; recover by using old version
        boot old version
    else
        # touch snappy stamp file to record we’ve attempted a boot
        write stamp file
    boot new version
else (snappy_mode=regular)
    boot current version
endif

The state needed to implement that logic is also stored and updated by the snappy system upgrader in snappy-system.txt. The snapppy_boot logic will create a snappy-stamp.txt to detect whether a first boot into a new system failed to boot and if thats the case will automatically fall back on next boot to the previously used system partition. Only if the boot succeeds the snappy-stamp.txt file will be removed and the changes to the primary boot partitions will be made permanent by the snappy runtime.

For Snappy’s U-Boot logic to work, these are the required U-Boot environment variables and U-Boot features:

  • CONFIG_SYS_HUSH_PARSER for things like if/then/else logic
  • FAT and MMC write support
  • ${mmcdev}:${mmcpart} is expected to point at the FAT boot partition
  • ${fdtfile} has the filename of the fdt file to load for this board
  • ${loadaddr}, ${initrd_addr}, ${fdt_addr} are the memory load addresses of the kernel, initrd and fdt
  • CONFIG_SUPPORT_RAW_INITRD is required to load a plain initrd (avoids the need to convert with mkimage)

Kernel requirements for Snappy (ubuntu and vanilla/android)

Snappy Kernels are based on the multiv7_defconfig plus some mandatory ubuntu configs available here. Based on the version of your kernel, rebase one of these tree on top your branch:

If your board does not work with the ubuntu generic armhf kernel, we have backports of apparmor patches to a few common android kernels available.

If you have a different kernel version, we recommend to port the patches that are committed on top of of our presquash kernel branches in those kernel trees.

Remember that all snappy systems must have all the latest apparmor patches applied to be fully functional, so even if you make a BSP not meant to power products you must enable those for your kernel.

Initrd requirements for Snappy

In snappy systems the initrd is mandatory as it contains important logic for our transactional system image upgrades. For porters the main usage for touching this file is to include driver modules that need to be available at boot time before the main system partition is mounted. For all other use cases, please contact the snappy development team to find a best practice that can be supported.

The ubuntu initrd.img is by default a lzma compressed init ramfs and is expected to be in the device tarballs assets/ directory. On a standard snappy disk layout you will find the initrd.img on the system-boot partition in the a/ directory, e.g.

$ sudo mount /dev/disk/by-label/system-boot /mnt
$ ls /mnt/a/
dtbs  hardware.yaml  initrd.img  vmlinuz

On modern Ubuntu installs as well as on your native snappy instance it can be unpacked using the following command line:

mkdir /tmp/myplace; cd /tmp/myplace
cat /mnt/a/initrd.img | unxz -c | cpio -i

This will yield a directory structure with the following layout:

$ ls
bin  conf  etc  init  lib  run  sbin  scripts

The modules for enabling early boot can be found in lib/modules/VERSION/kernel/, while the ubuntu-core system image logic can be found in scripts/ubuntu-core-rootfs. This logic needs to be shipped by all snappy initrds. After modifying this initrd you can package it up using the command:

find . | cpio --create --format=newc | lzma > /mnt/a/initrd.img

The initrd.img will then be used on next boot from that system-a partition.

And now … happy Snapping!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment