$ rust_network_driver/ ├── .github/ │ └── workflows/ │ ├── ci.yml # Kernel build + tests │ └── clippy.yml # Lints on every PR ├── src/ │ ├── lib.rs # Your module code │ └── tests.rs # KUnit tests ├── Cargo.toml # panic="abort" already set ├── Kbuild # Kernel build integration ├── Makefile # Wraps kernel build system ├── rust-toolchain.toml # Pinned nightly version └── .cargo/ └── config.toml # Target specification
rust_network_driver/ ├── .github/ │ └── workflows/ │ ├── ci.yml # Kernel build + tests │ └── clippy.yml # Lints on every PR ├── src/ │ ├── lib.rs # Your module code │ └── tests.rs # KUnit tests ├── Cargo.toml # panic="abort" already set ├── Kbuild # Kernel build integration ├── Makefile # Wraps kernel build system ├── rust-toolchain.toml # Pinned nightly version └── .cargo/ └── config.toml # Target specification
rust_network_driver/ ├── .github/ │ └── workflows/ │ ├── ci.yml # Kernel build + tests │ └── clippy.yml # Lints on every PR ├── src/ │ ├── lib.rs # Your module code │ └── tests.rs # KUnit tests ├── Cargo.toml # panic="abort" already set ├── Kbuild # Kernel build integration ├── Makefile # Wraps kernel build system ├── rust-toolchain.toml # Pinned nightly version └── .cargo/ └── config.toml # Target specification
[profile.dev] panic = "abort" opt-level = 0 [profile.release] panic = "abort" opt-level = 2 lto = true
[profile.dev] panic = "abort" opt-level = 0 [profile.release] panic = "abort" opt-level = 2 lto = true
[profile.dev] panic = "abort" opt-level = 0 [profile.release] panic = "abort" opt-level = 2 lto = true
#[cfg(CONFIG_RUST_KERNEL_TESTS)] #[kunit_tests(rust_network_driver_tests)] mod tests { use super::*; #[test] fn test_initialization() { let driver = NetworkDriver::new().unwrap(); assert!(driver.is_initialized()); } #[test] fn test_resource_cleanup() { { let _driver = NetworkDriver::new().unwrap(); // Drop happens here } // If cleanup failed, kernel would panic } }
#[cfg(CONFIG_RUST_KERNEL_TESTS)] #[kunit_tests(rust_network_driver_tests)] mod tests { use super::*; #[test] fn test_initialization() { let driver = NetworkDriver::new().unwrap(); assert!(driver.is_initialized()); } #[test] fn test_resource_cleanup() { { let _driver = NetworkDriver::new().unwrap(); // Drop happens here } // If cleanup failed, kernel would panic } }
#[cfg(CONFIG_RUST_KERNEL_TESTS)] #[kunit_tests(rust_network_driver_tests)] mod tests { use super::*; #[test] fn test_initialization() { let driver = NetworkDriver::new().unwrap(); assert!(driver.is_initialized()); } #[test] fn test_resource_cleanup() { { let _driver = NetworkDriver::new().unwrap(); // Drop happens here } // If cleanup failed, kernel would panic } }
make LLVM=1 test # Invokes: ./tools/testing/kunit/kunit.py run \ # --make_options LLVM=1 \ # --kconfig_add CONFIG_RUST_KERNEL_TESTS=y
make LLVM=1 test # Invokes: ./tools/testing/kunit/kunit.py run \ # --make_options LLVM=1 \ # --kconfig_add CONFIG_RUST_KERNEL_TESTS=y
make LLVM=1 test # Invokes: ./tools/testing/kunit/kunit.py run \ # --make_options LLVM=1 \ # --kconfig_add CONFIG_RUST_KERNEL_TESTS=y
KTAP version 1 # Subtest: rust_network_driver_tests ok 1 test_initialization ok 2 test_resource_cleanup ok 3 rust_network_driver_tests
KTAP version 1 # Subtest: rust_network_driver_tests ok 1 test_initialization ok 2 test_resource_cleanup ok 3 rust_network_driver_tests
KTAP version 1 # Subtest: rust_network_driver_tests ok 1 test_initialization ok 2 test_resource_cleanup ok 3 rust_network_driver_tests
name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest container: image: rust:nightly steps: - uses: actions/checkout@v4 - name: Install kernel build dependencies run: | -weight: 500;">apt-get -weight: 500;">update -weight: 500;">apt-get -weight: 500;">install -y \ llvm clang \ linux-headers-generic \ bindgen - name: Check Rust availability run: make LLVM=1 check-rust - name: Build module run: make LLVM=1 - name: Run tests run: make LLVM=1 test
name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest container: image: rust:nightly steps: - uses: actions/checkout@v4 - name: Install kernel build dependencies run: | -weight: 500;">apt-get -weight: 500;">update -weight: 500;">apt-get -weight: 500;">install -y \ llvm clang \ linux-headers-generic \ bindgen - name: Check Rust availability run: make LLVM=1 check-rust - name: Build module run: make LLVM=1 - name: Run tests run: make LLVM=1 test
name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest container: image: rust:nightly steps: - uses: actions/checkout@v4 - name: Install kernel build dependencies run: | -weight: 500;">apt-get -weight: 500;">update -weight: 500;">apt-get -weight: 500;">install -y \ llvm clang \ linux-headers-generic \ bindgen - name: Check Rust availability run: make LLVM=1 check-rust - name: Build module run: make LLVM=1 - name: Run tests run: make LLVM=1 test
impl NetworkDriver { pub fn new() -> Result<Self> { let buffer = DmaBuffer::alloc(DMA_SIZE) .map_err(|_| ENOMEM)?; let registers = ioremap(REGISTER_BASE) .ok_or(ENOMEM)?; Ok(Self { buffer, registers }) } }
impl NetworkDriver { pub fn new() -> Result<Self> { let buffer = DmaBuffer::alloc(DMA_SIZE) .map_err(|_| ENOMEM)?; let registers = ioremap(REGISTER_BASE) .ok_or(ENOMEM)?; Ok(Self { buffer, registers }) } }
impl NetworkDriver { pub fn new() -> Result<Self> { let buffer = DmaBuffer::alloc(DMA_SIZE) .map_err(|_| ENOMEM)?; let registers = ioremap(REGISTER_BASE) .ok_or(ENOMEM)?; Ok(Self { buffer, registers }) } }
# Install cargo-generate if you haven't cargo -weight: 500;">install cargo-generate # Generate your module cargo generate ---weight: 500;">git https://github.com/rust-kernel-template \ --name my_network_driver cd my_network_driver # Build against your kernel export KDIR=/path/to/linux-with-rust make LLVM=1 # Run tests make LLVM=1 test # Load the module -weight: 600;">sudo insmod my_network_driver.ko
# Install cargo-generate if you haven't cargo -weight: 500;">install cargo-generate # Generate your module cargo generate ---weight: 500;">git https://github.com/rust-kernel-template \ --name my_network_driver cd my_network_driver # Build against your kernel export KDIR=/path/to/linux-with-rust make LLVM=1 # Run tests make LLVM=1 test # Load the module -weight: 600;">sudo insmod my_network_driver.ko
# Install cargo-generate if you haven't cargo -weight: 500;">install cargo-generate # Generate your module cargo generate ---weight: 500;">git https://github.com/rust-kernel-template \ --name my_network_driver cd my_network_driver # Build against your kernel export KDIR=/path/to/linux-with-rust make LLVM=1 # Run tests make LLVM=1 test # Load the module -weight: 600;">sudo insmod my_network_driver.ko - Tests (KUnit integration requires manual wiring)
- CI pipeline (GitHub Actions needs kernel-specific configuration)
- Panic configuration (defaults vary by target, can silently break)
- rust-analyzer support (the rust-project.json generation is documented but not automated)
- Clippy integration (available but not configured by default) - Test module with #[kunit_tests] macro
- Kconfig entry enabling tests
- Make target that invokes kunit.py - LLVM toolchain (Rust kernel modules require LLVM, not GCC)
- Kernel headers with Rust metadata
- Specific bindgen version
- The right environment variables (LLVM=1, KDIR, target specification) - Module A: ENOSPC for allocation failures
- Module B: ENOMEM for allocation failures
- Module C: Custom error codes that didn’t map to kernel conventions - Upstream prefers in-tree modules : Abstractions submitted upstream require in-tree users, and patches should generally focus on in-tree development. If your goal is mainline merge, -weight: 500;">start with the official samples instead.
- Some subsystems lack Rust support : Individual subsystems have little built-in Rust support, and many kernel functions lack Rust abstractions. If you’re writing a driver for a subsystem without Rust bindings, you’ll spend time on unsafe FFI instead of high-level abstractions. - Out-of-tree drivers that won’t be upstreamed
- Internal company modules
- Rapid prototyping before upstream work
- Learning kernel module development in Rust - Modules targeting immediate upstream merge
- Subsystems with zero Rust abstractions (you’ll fight the template’s assumptions)
- Teams that want complete control over build configuration - Setup time: 4.2 hours average per driver
- Time to first test: 6.1 hours average
- Configuration bugs in CI: 11 total
- Forgotten panic=”abort”: 1 (discovered in production) - Setup time: 3 minutes average (just module name + generation)
- Time to first test: 12 minutes average (write test, run it)
- Configuration bugs in CI: 0
- Forgotten panic=”abort”: 0 - 🚀 Follow The Speed Engineer for more Rust, Go and high-performance engineering stories.
- 💡 Like this article? Follow for daily speed-engineering benchmarks and tactics.
- ⚡ Stay ahead in Rust and Go — follow for a fresh article every morning & night.