Table of Contents:

The guide will explain the process of modifying and packaging a custom kernel to be used within Apertis. Apertis is provided with a number of pre-built kernels, however it is likely that these may not suit your purpose:

  • The kernels don’t have the support for your SoC enabled
  • You wish to enable some specific drivers that aren’t enabled in the standard build
  • You have out-of-tree board support or drivers that you need to add
  • You wish to incorporate more drivers directly into the kernel rather than loading them as modules

In order to meet your project goals, it may be necessary to create a project specific kernel package for use in your project. This process can also be used when working on changes (such as bug fixes) that are to be proposed for inclusion in the Apertis kernels. Through this guide we are going to work through adding the following trivial kernel module.

Custom kernels

Custom kernels provide us with the means to tailor the kernel to a projects individual needs. It is recommended, where possible, to start with one of the Apertis kernels as a baseline as this will ease the process of integrating the kernel into the Apertis infrastructure. The Apertis kernels are packaged using the same technique that is used to package the Debian kernel packages.

The basic process is as follows:

  1. Retrieve required git repositories and artefacts
  2. Integrate changes into source tree
  3. Export changes as patches
  4. Integrate patches into packaging repository
  5. Build kernel package using gbp

Retrieve Required Git Repositories and Artefacts

Three items are required to work with Apertis kernels:

  1. A git repository of the kernel source tree
  2. The packaging repository
  3. An “original” source archive

The kernel source tree matching the revision of the binary kernel is required. This tree isn’t directly used during the generation of the binary kernel packages, but will be required in order to create any additional patches required by the project. Depending on how patched the binary kernel is the kernel source may require some or all of the patches added by the Debian build process to be applied to ensure the generated patches will apply during the build process.

There are currently multiple approaches that can be taken to getting a kernel source tree. For some packages a git repository is provided that has all the patches already integrated into it, this can be cloned and used. If this isn’t provided, it generally is enough to take the upstream kernel tree of the version on which the Apertis kernel is based and port patches to that. Bear in mind that such a tree will have some differences from the Apertis kernel and this may need to be taken into account by applying the patches from the Apertis kernel. The newest kernel provided by Apertis in the v2019 releases is based on the 4.19 super long term support stable tree, so we’ll clone that as a starting point:

$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git --branch linux-4.19.y

The packaging repository holds the debian/ meta data that will be used in following steps to generate the binary packages. The build tools covered in this guide expect this to be stored in git, like the current Apertis version. This should also be cloned:

$ git clone https://gitlab.apertis.org/packaging/linux.git --branch apertis/master linux-packaging

In order to ensure that the generated source artefacts are complete, the build process starts with a “original” source archive (one that is very close to one that may have been released by the upstream project, in this case the vanilla kernel source) and applies the patches provided in the packaging repository. Assuming that the customised kernel is to be based on an Apertis kernel, this can be downloaded from the Apertis package repository. If not, one can be generated with genorig.py. This needs to be save to a location where the build tools can find it, one option is to put original archives in ~/tarballs and point the build toos to this location. For the example, we will retrieve the Apertis archived source:

$ mkdir ~/tarballs
$ cd ~/tarballs
$ wget https://repositories.apertis.org/apertis/pool/target/l/linux/linux_4.19.20.orig.tar.xz

Integrating changes into source tree

The git kernel source repository, which is representative of that which will be used by Debian during compilation, can be used as a baseline for porting or developing additional features.

Existing patches can be imported using git am, which will import each patch as a separate commit. If developing additional drivers/board support it is advisable to break down this work into well explained atomic commits.

As a developer you will need to build and test your modifications during development, prior to completing packaging. There is no single recommended way to build the kernel prior to packaging as the most efficient way will depend on the development setup available to you. The suggested approach is to simply build the kernel image in the correct format and copy it to a location where the firmware can be made to boot the kernel.

In the kernel source, which was cloned to a directory called linux-stable, add the file drivers/misc/trivial.c containing the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: GPL-2.0

include <linux/init.h>
include <linux/module.h>

static int trivial_init(void)
{
    pr_info("Trivial module init\n");

    return 0;
}
module_init(trivial_init);

static void trivial_exit(void)
{
    pr_info("Trivial module exit\n");
}
module_exit(trivial_exit);

MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk");
MODULE_DESCRIPTION("Trivial Driver");
MODULE_LICENSE("GPL");

The following section needs to be patched into the configuration system in drivers/misc/Kconfig:

1
2
3
4
config TRIVIAL
    tristate "Trivial driver"
    ---help---
           Trivial example driver to show how to integrate code into the kernel.

Finally we need to add the following to the makefile drivers/misc/Makefile:

1
obj-$(CONFIG_TRIVIAL)       += trivial.o

Add and commit these changes to git:

$ git add drivers/misc/trivial.c drivers/misc/Kconfig drivers/misc/Makefile
$ git commit -m "Add trivial driver"

Export changes as patches

Once happy with the changes, and ensuring they have been committed as a series of clear atomic commits in the kernel git tree, they can be exported as patches using:

$ git format-patch HEAD~X

Where X is the number of commits to export as patches. This will create a series of numbered patches in the current directory. In our example:

$ git format-patch HEAD~1

Integrate patches into packaging repository

Debian packaging separates package specific changes from the “original” source, in this case the upstream Linux kernel. Note that in the case of the kernel, the Debian “original” source is lightly modified from the upstream tree to make the source comply with Debian policies (See README.source in the packaging repository).

The debian/patches/ directory contains patches that get applied to the original source. Typically Debian packages provide a single set of patches and a single series file that informs the patch management tool (typically quilt) the order in which to apply the patches. The kernel is a little different in this regard. Where as most packages are built for a handful of fairly generic architectures, the kernel is built multiple times with differing configurations and even patches applied. In order to accommodate this and to make it easier to track which patches are applicable to which platforms/features.

For our custom kernel we are going to only worry about a building for a “standard”, non-realtime, kernel. Create a directory debian/patches/apertis/<project_name> (where <project_name> is the name of the project. The patch names and paths (relative to the debian/patches/ directory) should be appended to the debian/patches/series file in the order in which they need to be applied.

For the example, make the directory debian/patches/apertis/example and copy in the generated patch:

$ cd linux-packaging
$ mkdir -p debian/patches/apertis/example
$ cp ../linux-stable/0001-Add-trivial-driver.patch debian/patches/apertis/example/

Add the following to the end of debian/patches/series:

1
2
# Trivial driver example
apertis/example/0001-Add-trivial-driver.patch

These changes need to be committed into the git repository:

# git add debian/patches/apertis/ debian/patches/series
# git commit -m "Add trivial driver kernel patch"
[master c7b36d0] Add trivial driver kernel patch
 2 files changed, 74 insertions(+)
 create mode 100644 debian/patches/apertis/example/0001-Add-trivial-driver.patch

Configuring kernel package

Adding the driver into the kernel is not enough to make it build, the config option that we have added (CONFIG_TRIVIAL) needs to be enabled. In this instance this can be done to build the driver into the kernel, or a a kernel module that can be loaded into the kernel at runtime.

The kernel is built for a large number of platform variants, whilst some of these have significant similarities to each other, and thus can use the same binary kernel, many different builds need to be performed to support boards which have different architectures. Still, there are certain kernel configuration options, typically those that aren’t linked to specific hardware, that we want to build for all platforms and those that are only required for specific hardware. In order to accommodate this without needless repetition of the kernel configuration, the configuration is built from a number of configuration fragments found in the packaging repository under debian/config/.

The main fragment for all platforms is debian/config/config, under the debian/config/ directory there are a number of directories for the individual architectures for which the kernel can be built. Under each of these directories there is another config file that is for the architecture specific options. There may also be one or more config.<sub_architecture> config files for configuration options specific to a sub-architecture build.

We will treat the trivial driver as architecture independent and build it as a module. Add the following to the end of debian/config/config:

1
CONFIG_TRIVIAL=m

This change will need committing:

user@apertis:~/Documents/example/linux-packaging$ git add debian/config/config
user@apertis:~/Documents/example/linux-packaging$ git commit -m "Enable trivial driver for all builds"
[apertis/master c01f005] Enable trivial driver for all builds
 1 file changed, 1 insertion(+), 1 deletion(-)

Update changelog

As we have now modified the Debian metadata, we should update the changelog. This is important as the version specified in the changelog determines that which will be used when creating the binary packages and thus whether the newly built kernel is considered as an upgrade from that already installed should you attempt an upgrade with apt.

Debian provides a helper, dch, to add a template entry into the changelog that must then be edited. We can pass the suffix we wish to use for this to the command with the -l option.

$ dch -l dev

The changelog (debian/changelog) should be opened in an editor and the changes made described, for example:

1
2
3
4
5
linux (4.19.20-1co5dev1) apertis; urgency=medium

  * Add and enable trivial driver example

 -- Martyn Welch <martyn.welch@collabora.co.uk>  Wed, 24 Apr 2019 09:55:43 +0100

This should also be committed to git:

$ git add debian/changelog
$ git commit -s -m "Updating changelog"

Build kernel package

First we need to ensure that git-buildpackage and other packages required for the build installed:

$ sudo apt install git-buildpackage kernel-wedge quilt bc

The following script can be used to perform the build (save as build-debian-kernel):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/sh
set -x

PARALLEL=$(/usr/bin/nproc)

export DEB_BUILD_PROFILES="pkg.linux.notools nodoc noudeb cross nopython"
export DEB_BUILD_OPTIONS=parallel=$PARALLEL

if [ -n "$1" ] ; then
  ARCH="-a$1"
else
  ARCH=""
fi

gbp buildpackage \
  --git-ignore-branch --git-overlay \
  --git-prebuild='debian/rules debian/control || true' \
  --git-builder='debuild -eDEBIAN_KERNEL_USE_CCACHE=1 -i -I' \
  --git-export-dir='~/build' \
  --git-tarball-dir='~/tarballs' \
  ${ARCH} \
  -B -d -uc -us

It can be run with the required architecture as a command line option. This will limit the build to the supplied architecture, rather than building for all available architectures, so using this option is recommended.

The script uses git build package (gbp) to generate the binary kernel packages. As mentioned before gbp needs to be able to find a original source archive. As we have the archive in ~/tarballs, this location is specified with the --git-tarball-dir option.

We also specify a build area via the --git-export-dir option. As we have specified this as ~/build, which causes the build and the build processes artifacts to be extracted/stored in a directory called build in the users home directory.

Let’s build the example for amd64:

$ sudo apt install libelf-dev
$ mkdir ~/build
$ cd linux-packaging
$ ../build-debian-kernel amd64

The built kernel packages will be available in ~/build.

Installing and testing

To test we are going to boot an Apertis APT-based minimal image on the Minnowboard. Download the image and write to an SD Card using bmaptool:

$ wget https://images.apertis.org/release/18.03/18.03.0/amd64/minimal/apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz
--2018-06-20 09:01:20--  https://images.apertis.org/release/18.03/18.03.0/amd64/minimal/apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz
Resolving images.apertis.org (images.apertis.org)... 46.235.227.200, 2a00:1098:0:82:1000:25:2eeb:e3c8
Connecting to images.apertis.org (images.apertis.org)|46.235.227.200|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 675847787 (645M) [application/x-gzip]
Saving to: ‘apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz’

apertis_18.03-minim 100%[===================>] 644.54M   642KB/s    in 13m 2s

2018-06-20 09:14:27 (844 KB/s) - ‘apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz’ saved [675847787/675847787]

$ wget https://images.apertis.org/release/18.03/18.03.0/amd64/minimal/apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap
--2018-06-20 09:16:06--  https://images.apertis.org/release/18.03/18.03.0/amd64/minimal/apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap
Resolving images.apertis.org (images.apertis.org)... 46.235.227.200, 2a00:1098:0:82:1000:25:2eeb:e3c8
Connecting to images.apertis.org (images.apertis.org)|46.235.227.200|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6806 (6.6K)
Saving to: ‘apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap’

apertis_18.03-minim 100%[===================>]   6.65K  --.-KB/s    in 0.001s

2018-06-20 09:16:06 (5.40 MB/s) - ‘apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap’ saved [6806/6806]

$ sudo bmaptool copy apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz /dev/sdh
bmaptool: info: discovered bmap file 'apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap'
bmaptool: info: block map format version 2.0
bmaptool: info: 1708985 blocks of size 4096 (6.5 GiB), mapped 359000 blocks (1.4 GiB or 21.0%)
bmaptool: info: copying image 'apertis_18.03-minimal-amd64-uefi_18.03.0.img.gz' to block device '/dev/sdh' using bmap file 'apertis_18.03-minimal-amd64-uefi_18.03.0.img.bmap'
bmaptool: info: 100% copied
bmaptool: info: synchronizing '/dev/sdh'
bmaptool: info: copying time: 2m 40.0s, copying speed 8.8 MiB/sec
$

Insert the SD card into the minnowboard and boot. We can see that it’s running a 4.4 kernel:

$ uname -a
Linux apertis 4.4.0-116-generic #140-Ubuntu SMP Wed Feb 28 08:44:14 UTC 2018 x86_64 GNU/Linux

Copy the new kernel deb file to the minnowboard (this can be done via ssh) and install it:

$ sudo mount -o remount,rw /
$ sudo dpkg -i linux-image-4.19.0-3-amd64_4.19.20-1co5dev1_amd64.deb
(Reading database ... 17693 files and directories currently installed.)
Preparing to unpack linux-image-4.19.0-3-amd64_4.19.20-1co5dev1_amd64.deb ...
Unpacking linux-image-4.19.0-3-amd64 (4.19.20-1co5dev1) over (4.4.38-0co2) ...
Setting up linux-image-4.19.0-3-amd64 (4.19.20-1co5dev1) ...
I: /vmlinuz.old is now a symlink to boot/vmlinuz-4.19.0-3-amd64
I: /initrd.img.old is now a symlink to boot/initrd.img-4.19.0-3-amd64
/etc/kernel/postinst.d/initramfs-tools:
update-initramfs: Generating /boot/initrd.img-4.19.0-3-amd64
W: Possible missing firmware /lib/firmware/i915/kbl_guc_ver9_14.bin for module i915
W: Possible missing firmware /lib/firmware/i915/bxt_guc_ver8_7.bin for module i915
/etc/kernel/postinst.d/zz-update-gummiboot:
Install 4.19.0-3-amd64 to ESP

Reboot and we boot with the new kernel:

$ uname -a
Linux apertis 4.19.0-3-amd64 #1 SMP Apertis 4.19.20-1co5dev1 (2018-06-05) x86_64 GNU/Linux

We now have the trivial driver available:

$ modinfo trivial
filename:       /lib/modules/4.19.0-3-amd64/kernel/drivers/misc/trivial.ko
license:        GPL
description:    Trivial Driver
author:         Martyn Welch <martyn.welch@collabora.co.uk
depends:
retpoline:      Y
intree:         Y
name:           trivial
vermagic:       4.19.0-3-amd64 SMP mod_unload modversions

Which we can load and unload, receiving the log messages when we do (we need to increase the log level seen on the console to get the messages):

$ sudo dmesg -n 7
$ sudo modprobe trivial
[  379.639027] Trivial module init
$ sudo rmmod trivial
[  386.541440] Trivial module exit