Adding an out-of-tree kernel package to the linux kernel in nixOS

Sat 31 December 2022
By makefu

Almost 4 years ago I bought the respeaker4 mic array<https://wiki.seeedstudio.com/ReSpeaker_4_Mic_Array_for_Raspberry_Pi/> for building my personal voice assistant.

That project never really manifested because setting up such a system was just not easy enough. However times have changed and with rhasspy<https://rhasspy.readthedocs.io/> it seems like a lot of painpoints are solved, at least in the Voice Assistant department. Machine-learning models do the heavy lifting to create text from speech, there is a working wakeword detection and rhasspy is even training these ML models to better extract the intent from your command.

I want all that on my Raspberry Pi 4 running NixOS<https://nixos.org/>.

With a "well supported" microphone array i thought the hardware is no problem at all, however it turns out the respeaker requires some extra tweaking to get it actually running on NixOS. On a "standard" raspbian with a raspberrypi 3b these issues might not have occurred.

I write about two nix-related topics:

  1. The configuration to set up the respeaker kernel module and Device Tree overlay
  2. How to package seeed-voicecard, an out-of-tree kernel module for NixOS

This article assumes existing understanding of NixOS as a concept, the Nix Language and how configuration and software is manged by the nix package manager. It will not be helpful anyone ho wants to learn about the concepts of the nix ecosystem.

Configure Respeaker kernel module

This is the configuration i ended up with down below i go through the important lines.

{ config, lib, pkgs, ... }:
let
  seeed-voicecard = (pkgs.callPackage seeed-voicecard.nix { kernel = config.boot.kernelPackages.kernel; }); # [1]
in
{
  imports = [ <nixos-hardware> ];                                  # [1]
  hardware.raspberry-pi."4".i2c1.enable = true;                    # [2]
  environment.systemPackages = [ pkgs.i2c-tools ];

  hardware.raspberry-pi."4".audio.enable = true;                   # [3]
  hardware.raspberry-pi."4".apply-overlays-dtmerge.enable = true;  # [4]

  boot.extraModulePackages = [ seeed-voicecard ];                  # [5]

  boot.initrd.kernelModules = [
    "snd-soc-seeed-voicecard"
    "snd-soc-ac108"
    "i2c-dev"                                                      # [6]
  ];

  sound.enable = true;                                             # [7]
  boot.loader.raspberryPi.firmwareConfig = [                       # [8]
    "dtparam=i2c_arm=on"
    "dtparam=i2s=on"
    "dtparam=spi=on"
  ];
  hardware.deviceTree = {
    enable = true;                                                 # [9]
    overlays = [
      { name = "respeaker-4mic"; dtsFile = "${seeed-voicecard}/lib/dts/seeed-4mic-voicecard-overlay.dts";}
    ];
  };
}

Package seeed-voicecard

The NixOS wiki provides an article about how packaging a kernel module looks like, but as always there are some caveats that may need some explanation.

To package the seed-voicecard i built upon the bkchr configuration on github<https://github.com/bkchr/nixos-config/blob/master/babyphone.nix>, thanks for this!

{ pkgs, lib, fetchFromGitHub, fetchpatch
,kernel                                                                  # [1]
, ... }:

pkgs.stdenv.mkDerivation rec {
  name = "seeed-voicecard-${version}-module-${kernel.modDirVersion}";
  version = "v4.1-post";

  src = fetchFromGitHub {                                                # [2]
    owner = "respeaker";
    repo = "seeed-voicecard";
    rev = "c52606626de050bdad85803d7e427a64cb0cf05c";
    hash = "sha256-sFReX9Nz9TDRvheKfPijRw1wQ++jJUk5+lOwVmfx3wA=";
  };

  KERNELDIR = "${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"; # [3]

  NIX_CFLAGS = ["-Wno-error=cpp"];

  patches = [                                                            # [4]
    (fetchpatch { url = "https://patch-diff.githubusercontent.com/raw/respeaker/seeed-voicecard/pull/323.patch";
                  hash = "sha256-coa0ZXDAGYxxi4ShL1HpOebfwOSmIpfdbEIYZtBWlYI="; })
  ];

  nativeBuildInputs = [ pkgs.perl ] ++ kernel.moduleBuildDependencies;
  buildInputs = [ pkgs.alsa-lib ];                                       # [5]

  buildPhase = ''
  make -C $KERNELDIR M=$(pwd) modules
  make -C ac108_plugin libasound_module_pcm_ac108.so                     # [7]
  sed -i "s/brcm,bcm2708/raspberrypi/" *.dts                             # [8]
  '';
  installPhase = ''
    mkdir -p $out/lib/modules/${kernel.modDirVersion}/sound/soc/codecs
    mkdir -p $out/lib/modules/${kernel.modDirVersion}/sound/soc/bcm
    cp snd-soc-wm8960.ko $out/lib/modules/${kernel.modDirVersion}/sound/soc/codecs
    cp snd-soc-ac108.ko $out/lib/modules/${kernel.modDirVersion}/sound/soc/codecs
    cp snd-soc-seeed-voicecard.ko $out/lib/modules/${kernel.modDirVersion}/sound/soc/bcm
    mkdir $out/lib/dts $out/lib/alsa-lib
    cp *.dts $out/lib/dts                                               # [9]
    cp ac108_plugin/libasound_module_pcm_ac108.so $out/lib/alsa-lib
  '';
}

Comments