Packaging help, maybe udev rule issue?

Hello! Back again, needing help to package yet another piece of software. As it turns out, my new audio interface needs this driver in order to get the full functionality and use the control GUI.

I went ahead a whipped up a quick Nix derivation by copying the package source for the control GUI, added a couple dependencies, and got it to install without errors, at least. However, I still get the same error message as before, indicating that the driver isn’t working as intended:

My derivation is as follows:

{
  lib,
  stdenv,
  fetchFromGitHub,
  pkg-config,
  makeWrapper,
  alsa-utils,
  alsa-lib,
  gtk4,
  json_c,
  libgcc,
  openssl,
  systemdLibs,
  wrapGAppsHook4,
  zlib
}:

stdenv.mkDerivation (finalAttrs: {
  pname = "fcp-support";
  version = "0.5";

  src = fetchFromGitHub {
    owner = "geoffreybennett";
    repo = "fcp-support";
    tag = finalAttrs.version;
    hash = "sha256-HqlmfjLKVzIJMsxG2SEslB/NxnMKQH6arM4CC+hoyJU=";
  };

  env.NIX_CFLAGS_COMPILE = toString [ "-Wno-error=deprecated-declarations" ];

  makeFlags = [
    "DESTDIR=\${out}"
    "PREFIX=''"
  ];

  nativeBuildInputs = [
    pkg-config
    wrapGAppsHook4
    makeWrapper
    json_c
    libgcc
    systemdLibs
  ];
  buildInputs = [
    gtk4
    alsa-lib
    openssl
    zlib
  ];

  # causes redefinition of _FORTIFY_SOURCE
  hardeningDisable = [ "fortify3" ];

  meta = {
    description = "Support tools for alsa controls presented by big Focusrite Scarlett Gen 4 Mixer Driver";
    homepage = "https://github.com/geoffreybennett/fcp-support";
    license = lib.licenses.gpl3Plus;
    platforms = lib.platforms.linux;
  };
})

Any ideas what I’m missing? I think possibly a udev rule?

I’m also pretty sure I have some unnecessary dependencies in there, as well.

Just to check before going further down that rabbit hole, lsmod lists fcp?

As for your package, by setting an installPhase you’re overriding the built-in installPhase that installs using make, so you’re probably not installing any of the actual binaries. Remove your installPhase. You can use nix-build or nix build to build that package, and look at the result directory it creates to see if you got it right. Share the file list so we can see it too.

There are udev rules, data files and even a systemd service which will need special handling. But before we can tackle all those, figure out what make install puts in your package’s result - then we can worry about creating a module that actually “installs” these things on NixOS.

2 Likes

Not seeing anything related to fcp when I do an lsmod unfortunately, but good call.

Oh goodness, that installPhase bit was an mis-paste, I’m not even sure how that got in there and I’ve removed it from the post.

When I nix-build without the installPhase junk, my results folder is structured like this:

image

image

Kinda looks like it’s installing as expected at least? Definitely need to get the udev rules, data files, and systemd service figured out next, like you said.

I actually took the liberty to fix your package while bored on public transport, just got home a little bit ago to test it:

# fcp-support.nix
{
  lib,
  stdenv,
  fetchFromGitHub,
  fetchpatch,
  pkg-config,
  alsa-lib,
  json_c,
  openssl,
  systemdLibs,
  zlib,
}:

stdenv.mkDerivation (finalAttrs: {
  pname = "fcp-support";
  version = "0.5";

  src = fetchFromGitHub {
    owner = "geoffreybennett";
    repo = "fcp-support";
    tag = finalAttrs.version;
    hash = "sha256-HqlmfjLKVzIJMsxG2SEslB/NxnMKQH6arM4CC+hoyJU=";
  };

  patches = [
    (
      # This fixes the hard-coded binary location in the systemd
      # service; It's already in the upstream's main branch, but the
      # project hasn't released in a while and I don't know if other
      # commits would cause issues.
      #
      # TODO: Remove this when updating, it's literally 3 or 4 commits
      # after the current tag.
      fetchpatch {
        url = "https://github.com/geoffreybennett/fcp-support/commit/da02cf769864758d39be99cf42079b69af51eb8e.patch";
        hash = "sha256-dHs+No4uqqMH0FYp4Wod0HLcsQ2MNI79UQVnNWwZ2kE=";
      }
    )
  ];

  makeFlags = [ "PREFIX=$(out)" ];

  nativeBuildInputs = [ pkg-config ];

  buildInputs = [
    alsa-lib
    json_c
    openssl
    systemdLibs
    zlib
  ];

  meta = {
    description = "Support tools for alsa controls presented by big Focusrite Scarlett Gen 4 Mixer Driver";
    homepage = "https://github.com/geoffreybennett/fcp-support";
    license = lib.licenses.gpl3Plus;
    platforms = lib.platforms.linux;
  };
})
Unstructured footnotes about my edits, too lazy to comment extensively
  • The fortify3 thing seems pointless, doubly-defining a variable to the same thing doesn’t hurt
  • Don’t put normal dependencies in nativeBuildInputs
  • You really don’t need gtk for this, I really don’t understand why you thought so?
  • Builds fine without disabling the deprecated-declarations warning

To use this package on NixOS, you need something along the lines of:

# fcp-support-module.nix
{ pkgs, ... }: let
  fcp-support = pkgs.callPackage ./fcp-support.nix { };
in {
  # Note that these already add the package to
  # `environment.systemPackages` as well, so you can just
  # define them and skip that.
  systemd.packages = [ fcp-support ];
  services.udev.packages = [ fcp-support ];

  # *Maybe*, doesn't hurt, especially if you want to
  # set the kernel module sysctl lock. I have no idea if this is
  # actually the correct module name, though, test with actual
  # hardware, please.
  boot.kernelModules = [ "fcp" ];

  # TODO: Add an assert or something to ensure the user
  # has at least kernel 6.14.
  # 
  # I guess it should be toggleable so that powerusers
  # can install an out-of-tree driver on older kernels, but
  # maybe I don't care enough about the two-month
  # window in which that will be relevant.
}

As far as I can tell everything else is correctly handled by setting the prefix. Pretty sensible makefile, would that more software was written like this…

I should probably consider upstreaming this, happy to do so if you are able to be a maintainer, but I wouldn’t want to do so alone without actually having the hardware to test this stuff with.


ETA:

Yes, that includes the data - as far as I understand the search path code, simply setting the prefix correctly should hard-code the correct nix store path into the search path.

3 Likes

Well I was able to test it, and it seems to work exactly as expected. The only kind of quirks are that it seems to absolutely detest connecting at boot, so the hardware has to be either power cycled or just disconnected and reconnected at boot. This kind of sucks because the power switch on the unit is awful quality. After power cycling, it works just fine.

The other thing is that I can’t seem to figure out is the correct kernel module to set as boot, as I’m still not seeing anything FCP or Focusrite specific when I do an lsmod. In either case, it seems to work without it, other than the aforementioned need to power cycle the unit.

I would be happy to be a maintainer if you want to upstream this. Not sure if it’ll benefit many people, though, since this driver is only needed for the 400+ USD units, but I’m definitely on board.

As for the unstructured footnotes, the unneeded dependencies, fority3 thing, and deprecated-declarations disable were just carryover from the expression for the GUI and I neglected to delete them before sharing here. The normal dependencies in nativeBuildInputs are just down to my lack of understanding, so apologies.

ETA: Thanks so much for your work on this. I sincerely appreciate it.

Hm, that could be caused by the udev rule not being present early enough. Hard to help you debug that, perhaps upstream would have some advice if you open an issue?

If you boot without it connected (better than just off, who knows if the controller picks it up when it’s not on anyway), save the contents of lsmod in a file, and then connect it, you can diff with another invocation of lsmod.

Let’s give it a shot :slight_smile:

1 Like

The dev for the driver and GUI has a Discord, so I’ve asked him there if this is at least normal behavior. I’ll probably update here if he has any insight on the udev rule.

The diff yields a ton of snd modules, which to me makes it sound like the fcp stuff is included in those? That would make sense to me anyway. Is there a benefit that you’re aware of to specifying any of those in my boot.kernelModules?

❯ diff lsmod1.txt lsmod2.txt
1a2,8
> sd_mod                 86016  0
> snd_usb_audio         610304  2
> snd_ump                36864  1 snd_usb_audio
> snd_usbmidi_lib        53248  1 snd_usb_audio
> uas                    36864  0
> mc                     90112  1 snd_usb_audio
> usb_storage            94208  1 uas
97c104
< snd_hwdep              24576  1 snd_hda_codec
---
> snd_hwdep              24576  3 snd_usb_audio,snd_hda_codec
103c110
< snd_pcm               204800  4 snd_hda_codec_hdmi,snd_hda_intel,snd_hda_codec,snd_hda_core
---
> snd_pcm               204800  5 snd_hda_codec_hdmi,snd_hda_intel,snd_usb_audio,snd_hda_codec,snd_hda_core
138c145
< snd_rawmidi            57344  0
---
> snd_rawmidi            57344  2 snd_usbmidi_lib,snd_ump
140c147
< snd_seq_device         20480  2 snd_seq,snd_rawmidi
---
> snd_seq_device         20480  3 snd_seq,snd_ump,snd_rawmidi
142c149
< snd                   159744  13 snd_seq,snd_seq_device,snd_hda_codec_hdmi,snd_hwdep,snd_hda_intel,snd_hda_codec,snd_timer,snd_pcm,snd_rawmidi
---
> snd                   159744  19 snd_seq,snd_seq_device,snd_hda_codec_hdmi,snd_hwdep,snd_hda_intel,snd_usb_audio,snd_usbmidi_lib,snd_hda_codec,snd_timer,snd_ump,snd_pcm,snd_rawmidi
171c178
< scsi_mod              339968  1 libata
---
> scsi_mod              339968  4 sd_mod,usb_storage,uas,libata
173c180
< scsi_common            16384  2 scsi_mod,libata
---
> scsi_common            16384  5 scsi_mod,sd_mod,usb_storage,uas,libata

If you specify all the modules you need, you can set security.lockKernelModules = true; to lock down kernel module loads, which will thwart a lot of potential CVEs in under-maintained kernel modules (and given the big wave of CVE discoveries, I think this is desirable, though some people think it is a bit too paranoid, and actually using it makes it hard to hotplug devices you don’t use all the time for obvious reasons). Explicitly adding this kernel module in the NixOS module makes that a little easier for users.

It’d also make it pretty obvious in dmesg when someone is using a kernel that doesn’t have this driver yet.

No other real benefits besides that, it’s just a nice-to-have for the module.

1 Like

I suppose I should update here to say the dev responded to my question and basically just said what I’m experiencing is normal. He said power cycling the unit is the easiest solution, which I’ve found to be true. He also mentioned this CLI command could be used to trigger the driver manually, but I haven’t gotten it to work and don’t necessarily care about that:

sudo udevadm trigger --action=add /sys/class/sound/controlC0

I’ve also found that this unit aggravates the Nvidia bug that I experienced with my previous audio interface, causing Steam games to not properly initialize an audio source, which also causes some of them to crash. The workaround is once again to utilize the power switch, turning off the unit while the game launches, then turning it back on to actually play.