Contents

Recently, I ran into a situation where the driver for my Ethernet controller had a bug. In my case, the igc driver did not update the /dev/net/proc stats, such that all of the stats would persistently remain at 0 for that particular Ethernet interface. While debugging this issue and developing a fix, I needed a way to compile the driver and load the driver alongside the Ubuntu kernel.

Dynamic Kernel Module Support (dkms)

DKMS allows you to dynamically load a kernel module (e.g., a driver for a graphics card or ethernet controller) into a pre-compiled kernel, such as a kernel provided by a Linux distribution (e.g., Ubuntu, Fedora, Arch, etc.).[1]See Wikipedia DKMS also automatically compiles and installs the module when a new kernel is installed. For example, when installing a DKMS module, the source code of the module may be copied to /usr/src, which is where DKMS will look to find the source code to rebuild the module when a new kernel is installed.

The Arch wiki pages on compiling a kernel module and dksm are helpful, but this article aims to merge both of these topics. For example, when you want to prepare or tweak your own kernel module, and you would like to use dkms to manage the installation process. The CentOS wiki also provides helpful instructions on how to build a kernel module using dkms, but lacks some specific issues you may encounter on Ubuntu.

Prerequisites

You may need to install some packages:

sudo apt install dkms dpkg-dev git

Step 1. Prepare the project folder for the dkms build

Under this tutorial, the project folder for the driver source code will have the following structure:

  • driver-project/
    • dkms.conf
    • install.sh
    • uninstall.sh
    • README
    • LICENSE
    • src/
      • driver.h
      • driver.c

Make the directories:

mkdir driver-project
cd driver-project
mkdir src

Of course, change the name of driver-project to suit your preferences or needs.

You will be customizing the dkms.conf, install.sh, and uninstall.sh files as further described in this tuturial.

The README and LICENSE files can be optional. But you may want to review the license of any source code you will be modifying in case you need to include that license with your derivative module.

The source code will reside in src/, and the files driver.h and driver.c are just examples.

Download source code using git

If you are going to be modifying or forking an existing driver included in a Linux kernel, you will need to download the source code.

For git, you can download the entire source code of the kernel with the clone command, for example:

git clone https://github.com/torvalds/linux.git

Alternatively, you can download the source code as a ZIP file:

wget https://github.com/torvalds/linux/archive/master.zip

You can also download a specific subdirectory using svn, for example:

svn export https://github.com/torvalds/linux.git/trunk/drivers/net/ethernet/intel/igc

The syntax for the repo URL of the svn export command is broken in to three parts:

<url://repo.git>/<branch>/<subdirectory>

  1. url://repo.git is the project URL (such as https://github.com/torvalds/linux.git).
  2. branch represents the branch of the project to which the source code belongs. trunk is used for the master branch, and other branches use their respective title. You can list the branches available with svn list url://repo.git
  3. subdirectory is the specific subdirectory you would like to download that is residing under the master branch (for example, drivers/net/ethernet/intel/igc).

Download source code of an Ubuntu package

For an Ubuntu/Debian package, you can download the source with:

apt source package

Most likely, you will need to enable the source repository for that package. You can do that by first identifying the repository that hosts the package:

apt policy package

In /etc/apt/sources.list, uncomment the deb-src lines corresponding to the repositories listed by the apt policy command above. For example, the deb-src line below

deb http://us.archive.ubuntu.com/ubuntu/ focal main restricted
# deb-src http://us.archive.ubuntu.com/ubuntu/ focal main restricted

will change to:

deb http://us.archive.ubuntu.com/ubuntu/ focal main restricted
deb-src http://us.archive.ubuntu.com/ubuntu/ focal main restricted

Then you will download the source code of the Ubuntu package with:

apt source package

Often, you will see a message after running apt source providing a notice that the “packaging is maintained in the ‘Git’ version control system” with a URL to the git package.

Download source code of an Ubuntu kernel

To download the source code of an Ubuntu kernel, it is a similar process for a given package as described above. Most likely, you will want to download the source code of the kernel that you are currently running.

deb http://us.archive.ubuntu.com/ubuntu/ focal-updates main restricted
deb-src http://us.archive.ubuntu.com/ubuntu/ focal-updates main restricted
. . .
deb http://security.ubuntu.com/ubuntu focal-security main restricted
deb-src http://security.ubuntu.com/ubuntu focal-security main restricted

Then you will download the source code of the Ubuntu kernel:

apt source linux-image-unsigned-`uname -r`

Often, you will see a message after running apt source providing a notice that the “packaging is maintained in the ‘Git’ version control system” with a URL to the git package. You could use git clone next time, if you want to pull the latest source code.

Also, other Ubuntu-based distributions may not require the use of linux-image-unsigned. For example, to download the latest Pop OS! kernel source you can use:

apt source linux-image-`uname -r`

Copy source code to src/

Once the source code is downloaded, copy or move it to the src/ directory of your driver project directory:

cd driver-project/
cp -rv source-code-dir/* src/.

where source-code-dir is the directory where the source code resides. For example, suppose you downloaded the 5.6 kernel and wanted the source code for a particular Intel ethernet controller, such as the igc driver. You would copy that source code as follows:

cp -rv linux-5.6.19/drivers/net/ethernet/intel/igc/* scr/.

Step 2. Configure DKMS

Create the dkms.conf file:

cd driver-project/
vi dkms.conf

Let’s populate the dkms.conf with the following:

PACKAGE_NAME="driver-project-name"
PACKAGE_VERSION=unique-version
MAKE="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build/src modules"
CLEAN="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build/src clean"
BUILT_MODULE_NAME[0]="driver-name"
BUILT_MODULE_LOCATION[0]="src"
DEST_MODULE_LOCATION[0]="/updates"
REMAKE_INITRD=no
AUTOINSTALL=yes

You will be modifying the following parameters:

  • PACKAGE_NAME – This directive is used to give the name associated with the entire package of modules. This is the same name that is used with the -m option when building, adding, etc. and may not necessarily be the same as the MODULE_NAME. This directive must be present in every dkms.conf.[2]See the dkms man page
  • PACAKGE_VERSION – This directive is used to give the version associated with the entire package of modules being installed within that dkms package. This directive must be present in every dkms.conf.[3]See the dkms man page
  • BUILT_MODULE_NAME[0] – This directive gives the name of the module just after it is built. If your DKMS module package contains more than one module to install, this is a required directive for all of the modules. This directive should explicitly not contain any trailing “.o” or “.ko”.[4]See the dkms man page

BUILT_MODULE_LOCATION[0]=”src” indicates that the source code of the driver is located in the src/.

As an example, the dkms.conf may have the following settings for the igc driver:

PACKAGE_NAME="igc"
PACKAGE_VERSION=5.4.0-7642.46
MAKE="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build/src modules"
CLEAN="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build/src clean"
BUILT_MODULE_NAME[0]="igc"
BUILT_MODULE_LOCATION[0]="src"
DEST_MODULE_LOCATION[0]="/updates"
REMAKE_INITRD=no
AUTOINSTALL=yes

In this case, the PACKAGE_VERSION indicated that the source code for the igc derived from the 5.4.0-7642.46 Pop OS! kernel.

Step 3. Install the driver

Set the version of the driver module using the VERSION variable. The VERSION variable will match the PACKAGE_VERSION as set in the dkms.conf above.

VERSION=5.4.0-7642.46

Add the module to the DKSM tree:

cd driver-project
dkms add .

Build the module using DKMS:

dkms build igc -v ${VERSION}

Install the module using DKMS:

dkms install --force igc -v ${VERSION}

The –force option can be used if you want this version of the driver to have priority over the same driver built even with the current kernel.

Check that the module has been installed using the modinfo command:

modinfo igc

If the filename attribute contains updates/dkms in the path, then you have successfully installed the driver.

Step 4. Load the driver

You man need to remove the existing module from the kernel with:

rmmod igc

Load the driver with modprobe:

modprobe igc

Scripts to automate installation and uninstallation

install.sh:

#!/bin/bash

VERSION=5.4.0-7642.46
dkms add .
dkms build igc -v ${VERSION}
dkms install --force igc -v ${VERSION}

uninstall.sh:

#!/bin/bash

VERSION=5.4.0-7642.46 
dkms remove -m igc -v ${VERSION} --all

Footnotes