Skip to content

Latest commit

 

History

75 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xdp-backports

build

Out-of-tree Linux NIC drivers adding native XDP support to legacy hardware commonly used for EtherCAT.

Official repository: https://github.com/stratuMAK/xdp-backports

Motivation

Userspace EtherCAT masters (such as stratuMAK/ethercat) benefit greatly from XDP: eBPF programs run directly in the driver's RX hot path — before any sk_buff allocation — enabling sub-microsecond packet steering with zero kernel-network-stack overhead.

Many NICs commonly used for EtherCAT do not support XDP upstream and likely never will. This project provides out-of-tree driver builds that retrofit native XDP plumbing onto those legacy devices, targeting Linux 6.1+ for the PCIe drivers and 6.12+ for the SoC drivers (including PREEMPT_RT kernels).

Supported Drivers

Directory NIC Upstream source Minimum kernel Status
macb/ Cadence MACB/GEM (Raspberry Pi, Zynq, …) raspberrypi/linux rpi-6.18.y 6.12 ✅ Working
e1000e/ Intel PRO/1000 (I217/I218/I219, 82574, …) torvalds/linux v6.18 6.1 ✅ Working
genet/ Broadcom GENET (Raspberry Pi 3/4, …) raspberrypi/linux rpi-6.18.y 6.12 ✅ Working
r8169/ Realtek RTL8111/8168/8125 torvalds/linux v6.18 6.1 ✅ Working

The minimum kernel is per driver, and each is verified in CI on the kernels it claims:

  • Debian 12 (6.1) on x86_64 — e1000e, r8169
  • Debian 13 (6.12) on x86_64 — all four
  • Raspberry Pi (6.12, 6.18) on arm64 — all four

macb and genet are SoC drivers for hardware that does not appear on Debian x86_64 systems, so they are not supported below 6.12; building them there would need substantial backporting for no practical gain.

Note that a stable point release matters, not just the series: e1000e builds on 6.1.180 and on Debian's 6.1.0-rt-amd64 but not on a vanilla 6.1.0, because APIs it needs reached 6.1.y through stable backports. The sharpest of these is <net/netdev_queues.h>, which 6.1.y only gained between 6.1.112 and 6.1.115 — that is the effective floor for the 6.1 series.

Features

  • Page pool RX — zero-copy buffer management via page_pool.
  • XDP core.ndo_bpf program attach/detach, RX verdict handling (XDP_PASS, XDP_DROP, XDP_TX, XDP_REDIRECT, XDP_ABORTED).
  • XDP redirect.ndo_xdp_xmit for cross-device forwarding.
  • Compat layer — builds against kernel headers from Linux 6.1.115 and from 6.4 through 6.19 (6.12+ for macb/genet), including PREEMPT_RT variants. The 6.2/6.3 gap is deliberate: those series never got <net/netdev_queues.h> and are EOL. Every version gate is verified against the upstream tag that first carries the symbol — see compat/version-gates.md for the full table — and the kernels listed under Continuous Integration are additionally build-tested.

Build

Requires kernel headers matching the running kernel.

# Build all drivers
make

# Build a single driver
make -C macb/
make -C e1000e/
make -C genet/
make -C r8169/

# Install (replace in-tree modules)
sudo insmod macb/macb_xdp.ko
sudo insmod e1000e/e1000e_xdp.ko
sudo insmod genet/genet_xdp.ko
sudo insmod r8169/r8169_xdp.ko

# Clean
make clean

DKMS Packages

Each driver is packaged as a Debian DKMS package that auto-rebuilds the module on kernel upgrades.

Prebuilt .deb packages for every release are attached to the GitHub releases — they are Architecture: all (DKMS ships source and builds on the target), so the same package works on x86_64 and arm64:

sudo apt install dkms
sudo dpkg -i macb-xdp-dkms_*.deb

To build them yourself:

sudo apt install dpkg-dev debhelper dkms dh-dkms

# Build all .deb packages (they land in the repo root)
make deb

# Install a package
sudo dpkg -i macb-xdp-dkms_*.deb
sudo dpkg -i e1000e-xdp-dkms_*.deb
sudo dpkg -i genet-xdp-dkms_*.deb
sudo dpkg -i r8169-xdp-dkms_*.deb

# Remove a package
sudo dpkg -r macb-xdp-dkms

Each package blacklists the matching in-tree module via /etc/modprobe.d/. Because the initramfs carries its own copy of /etc/modprobe.d (and, with Debian's default MODULES=most, of every net driver), the packages activate the update-initramfs dpkg trigger — otherwise the stale image would keep loading the in-tree driver at early boot, where it claims the device and the _xdp module can no longer bind. A reboot is needed for the swap to take effect on an interface that is already up.

Versioning

debian/changelog is the single source of truth for the package version. DKMS embeds that version in its /usr/src/<pkg>-<version>/ path, so dkms.conf and debian/<pkg>.install are generated at build time from their .in templates by debian/rules — do not edit the generated files.

To bump the version across all four drivers:

# explicit entry text
scripts/set-version.sh 6.18.36+xdp2 "What changed."

# or derive the entries from git (what the release workflow does)
scripts/set-version.sh --from-git 6.18.36+xdp2

With --from-git the changelog entries are the commit subjects since the previous tag, filtered per driver to the paths that end up in that driver's package — so genet's changelog does not list macb fixes. The committed changelog stays valid on its own, so make deb still works in a fresh clone without git history.

Continuous Integration

Workflow Trigger What it does
build.yml push, PR Compile-tests each driver on the kernels it supports (see below), builds and verifies the .deb packages, uploads them as artifacts
release.yml tag v* Runs the full compile matrix, stamps the version from the tag, builds the four .debs and publishes them on a GitHub release with checksums

Cutting a release is therefore just:

git tag v6.18.36+xdp2
git push origin v6.18.36+xdp2

Push the tag only — do not create the release in the web UI. Doing so creates the tag as a side effect, and the workflow then finds a release it did not make. It recovers (it fills the existing release in rather than failing), but the release ends up with whatever title and body the UI set.

The compile matrix uses mainline tarballs prepared with scripts/prepare-kernel.sh, which you can also run locally:

sudo apt install bc bison flex libelf-dev libssl-dev rsync
KDIR=$(scripts/prepare-kernel.sh 6.1.180 x86_64 /tmp/kernels)
make KDIR="$KDIR" DRIVERS="e1000e r8169"

The matrix has one entry per supported deployment target rather than a full cartesian product, so each driver is built only on the kernels it claims:

Kernel Arch Drivers Target
6.1.180 x86_64 e1000e, r8169 Debian 12
6.12.100 x86_64, arm64 all four Debian 13, Raspberry Pi
6.18.41 x86_64, arm64 all four Raspberry Pi

prepare-kernel.sh starts from defconfig and explicitly enables the options the drivers' headers depend on (OF, phylib, phylink, PTP, XDP), so the SoC drivers are compile-tested on x86_64 as well as arm64.

The matrix pins the current point release of each series, not the .0 release. This matters: stable backports move APIs into older series, so e1000e builds fine on 6.1.180 (and on Debian's 6.1.0-rt-amd64) while failing against a vanilla 6.1.0 that nobody actually runs. Bump the pinned versions periodically against https://www.kernel.org/releases.json.

Usage

# Attach an XDP program
ip link set dev eth0 xdp obj xdp_pass.o sec xdp

# Detach
ip link set dev eth0 xdp off

Works with the stratuMAK/ethercat userspace EtherCAT master using the xdp-native transport.

Tested on

  • Raspberry Pi 4/5 (aarch64), kernel 6.18-rt
  • Intel I219-LM (x86_64), kernel 6.1-rt
  • Realtek RTL8111/8168 (x86_64)
  • 3 EtherCAT slaves via native XDP transport

Known limitations

The RX path (page_pool + XDP redirect, as used by the AF_XDP EtherCAT transport in copy mode) is the field-tested path on all drivers. The XDP TX paths (XDP_TX and redirect into these devices via ndo_xdp_xmit) are code-reviewed and fixed but have seen less runtime exposure.

  • macb: GEM RX runs with a zero receive-buffer offset (RBOF) and non-IP-aligned eth data, like other XDP drivers; on NET_IP_ALIGN != 0 architectures (32-bit ARM) this builds and is believed structurally correct, but has never been runtime-tested there.

  • e1000e: XDP requires MTU ≤ 1518. Above that the driver receives each frame as a chain of 2048-byte page segments and there is no multi-buffer XDP support, so attaching a program at jumbo MTU (and raising the MTU into jumbo range while a program is attached) is rejected. Jumbo frames without XDP are unaffected.

Note that dev->xdp_features does not gate native program attach (ndo_bpf alone decides that); it is required for these devices to be valid XDP_REDIRECT targets and for correct capability reporting.

Adding more drivers

Contributions welcome! The general pattern is:

  1. Copy the upstream driver source into a new subdirectory.
  2. Add a compat.h shim for kernel-version differences.
  3. Wire up page_pool RX, ndo_bpf, and ndo_xdp_xmit.
  4. Add a Makefile/Kbuild for out-of-tree building.

License

Each driver subdirectory retains its original upstream license (GPL-2.0).

About

XDP support backported to widely used NIC drivers

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages