Recently, I bought a USB 3.0 2.5 Gbps Ethernet dongle for my Atomic Pi router. This dongle requires a version of the r8152 kernel driver with support for the RTL8156 chipset, which is only added in Linux 5.13. Now, I am running the Debian stable kernel and have no wish to backport the latest 5.13 kernel simply for that one driver. So of course, I came up with an approach to backport a driver from a newer version.

In this blog post, I will walk you through the process of backporting a single kernel module, using the r8152 kernel driver as an example.

To start, we create a directory called r8152-backport, where we will be storing files related to this exercise, and fetch the latest version of the kernel module:

$ mkdir r8152-backport
$ cd r8152-backport
$ wget https://raw.githubusercontent.com/torvalds/linux/master/drivers/net/usb/r8152.c

We can then create a simple Makefile to build the kernel. Save the following block as Makefile, and remember to replace r8152 with your module name if you are backporting something else:

obj-m += r8152.o
USER  := $(shell whoami)
KVER ?= $(shell uname -r)
KDIR ?= /lib/modules/$(KVER)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

.PHONY: test

We can then run make to build this module:

$ make
make -C /lib/modules/5.10.0-8-amd64/build M=/home/quantum/build/r8152-backport modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.0-8-amd64'
  CC [M]  /home/quantum/build/r8152-backport/r8152.o
/home/quantum/build/r8152-backport/r8152.c:29:10: fatal error: linux/usb/r8152.h: No such file or directory
   29 | #include <linux/usb/r8152.h>
      |          ^~~~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [/usr/src/linux-headers-5.10.0-8-common/scripts/Makefile.build:284: /home/quantum/build/r8152-backport/r8152.o] Error 1
make[2]: *** [/usr/src/linux-headers-5.10.0-8-common/Makefile:1845: /home/quantum/build/r8152-backport] Error 2
make[1]: *** [/usr/src/linux-headers-5.10.0-8-common/Makefile:185: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-5.10.0-8-amd64'
make: *** [Makefile:7: all] Error 2

It appears that this module requires its own header file. Fortunately, this is easily remedied. We simply have to download r8152.h and make r8152.c include our version. You can find the file by searching the kernel source tree. This is one way to do it:

$ wget https://raw.githubusercontent.com/torvalds/linux/master/include/linux/usb/r8152.h
$ sed -i '/r8152\.h/c\#include "r8152.h"' r8152.c

Now this kernel module should build. Let’s see what happens:

$ make
make -C /lib/modules/5.10.0-8-amd64/build M=/home/quantum/build/r8152-backport modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.0-8-amd64'
  CC [M]  /home/quantum/build/r8152-backport/r8152.o
/home/quantum/build/r8152-backport/r8152.c:8852:12: warning: ‘struct kernel_ethtool_coalesce’ declared inside parameter list will not be visible outside of this definition or declaration
 8852 |     struct kernel_ethtool_coalesce *kernel_coal,
      |            ^~~~~~~~~~~~~~~~~~~~~~~
/home/quantum/build/r8152-backport/r8152.c:8873:12: warning: ‘struct kernel_ethtool_coalesce’ declared inside parameter list will not be visible outside of this definition or declaration
 8873 |     struct kernel_ethtool_coalesce *kernel_coal,
      |            ^~~~~~~~~~~~~~~~~~~~~~~
/home/quantum/build/r8152-backport/r8152.c:9088:18: error: initialization of ‘int (*)(struct net_device *, struct ethtool_coalesce *)’ from incompatible pointer type ‘int (*)(struct net_device *, struct ethtool_coalesce *, struct kernel_ethtool_coalesce *, struct netlink_ext_ack *)’ [-Werror=incompatible-pointer-types]
 9088 |  .get_coalesce = rtl8152_get_coalesce,
      |                  ^~~~~~~~~~~~~~~~~~~~
/home/quantum/build/r8152-backport/r8152.c:9088:18: note: (near initialization for ‘ops.get_coalesce’)
/home/quantum/build/r8152-backport/r8152.c:9089:18: error: initialization of ‘int (*)(struct net_device *, struct ethtool_coalesce *)’ from incompatible pointer type ‘int (*)(struct net_device *, struct ethtool_coalesce *, struct kernel_ethtool_coalesce *, struct netlink_ext_ack *)’ [-Werror=incompatible-pointer-types]
 9089 |  .set_coalesce = rtl8152_set_coalesce,
      |                  ^~~~~~~~~~~~~~~~~~~~
/home/quantum/build/r8152-backport/r8152.c:9089:18: note: (near initialization for ‘ops.set_coalesce’)
/home/quantum/build/r8152-backport/r8152.c:9197:3: error: ‘const struct net_device_ops’ has no member named ‘ndo_eth_ioctl’; did you mean ‘ndo_do_ioctl’?
 9197 |  .ndo_eth_ioctl  = rtl8152_ioctl,
      |   ^~~~~~~~~~~~~
      |   ndo_do_ioctl
/home/quantum/build/r8152-backport/r8152.c:9197:20: error: initialization of ‘netdev_tx_t (*)(struct sk_buff *, struct net_device *)’ {aka ‘enum netdev_tx (*)(struct sk_buff *, struct net_device *)’} from incompatible pointer type ‘int (*)(struct net_device *, struct ifreq *, int)’ [-Werror=incompatible-pointer-types]
 9197 |  .ndo_eth_ioctl  = rtl8152_ioctl,
      |                    ^~~~~~~~~~~~~
/home/quantum/build/r8152-backport/r8152.c:9197:20: note: (near initialization for ‘rtl8152_netdev_ops.ndo_start_xmit’)
cc1: some warnings being treated as errors
make[3]: *** [/usr/src/linux-headers-5.10.0-8-common/scripts/Makefile.build:284: /home/quantum/build/r8152-backport/r8152.o] Error 1
make[2]: *** [/usr/src/linux-headers-5.10.0-8-common/Makefile:1845: /home/quantum/build/r8152-backport] Error 2
make[1]: *** [/usr/src/linux-headers-5.10.0-8-common/Makefile:185: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-5.10.0-8-amd64'
make: *** [Makefile:7: all] Error 2

Oh no! It appears that the kernel interface has changed in the meantime. We have two options here: either use an older version of this module before this change, or we can attempt to revert it. Either way, we’ll need to find when this change happened. Doing a quick git blame shows that the changes were introduced in f3ccfda19319 and a76053707dbf. We can use the code from the commit before the earlier of the two, which in this case is a554bf96b49d, the parent commit of a76053707dbf. However, I am just going to revert the two changes:

$ curl -sSL https://github.com/torvalds/linux/commit/f3ccfda1931977b80267ba54070a1aeafa18f6ca.diff | sed -n '/^diff.*r8152\.c/,/^diff/{/^diff/d;p}' | patch -R r8152.c
patching file r8152.c
$ curl -sSL https://github.com/torvalds/linux/commit/a76053707dbf0dc020a73b4d90cd952409ef3691.diff | sed -n '/^diff.*r8152\.c/,/^diff/{/^diff/d;p}' | patch -R r8152.c
patching file r8152.c
Hunk #1 succeeded at 9190 (offset 17 lines).

Remember, we are only interested in changes to r8152.c, so we use sed to filter out those changes.

Now, the kernel module should build:

$ make
make -C /lib/modules/5.10.0-8-amd64/build M=/home/quantum/build/r8152-backport modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.0-8-amd64'
  CC [M]  /home/quantum/build/r8152-backport/r8152.o
  MODPOST /home/quantum/build/r8152-backport/Module.symvers
  CC [M]  /home/quantum/build/r8152-backport/r8152.mod.o
  LD [M]  /home/quantum/build/r8152-backport/r8152.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.0-8-amd64'

We can now sudo insmod r8152.ko to load our version of the module, but it’s probably better to use dkms to manage this module. To do this, we first need to create dkms.conf:

PACKAGE_NAME="r8152"
PACKAGE_VERSION="5.15.0"
MAKE[0]="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build modules"
CLEAN="rm src/r8152.ko src/*.o || true"
BUILT_MODULE_NAME[0]="r8152"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"

If you are backporting another module, you probably want to change r8152 to the name of your module. For PACKAGE_VERSION, I set it to the kernel version I sourced the module from.

We can then install the module with dkms:

$ sudo dkms install .

Creating symlink /var/lib/dkms/r8152/5.15.0/source ->
                 /usr/src/r8152-5.15.0

DKMS: add completed.

Kernel preparation unnecessary for this kernel.  Skipping...

Building module:
cleaning build area...
make -j24 KERNELRELEASE=5.10.0-8-amd64 -C /lib/modules/5.10.0-8-amd64/build M=/var/lib/dkms/r8152/5.15.0/build modules...
cleaning build area...

DKMS: build completed.

r8152.ko:
Running module version sanity check.
 - Original module
 - Installation
   - Installing to /lib/modules/5.10.0-8-amd64/updates/dkms/

depmod...

DKMS: install completed.

If you ever need to uninstall the module, simply run:

$ sudo dkms uninstall r8152/5.15.0 --all