Copying Custom EDID

Hello,

I’m trying to get a custom EDID for my monitor copied over to the /lib/firmware/ folder (under an EDID subdirectory). I am then using this EDID as part of my kernel params.

I have this in my nix.conf:

  hardware.firmware = [
    (pkgs.runCommandNoCC "firmware-custom-edid" {} ''
      mkdir -p $out/lib/firmware/edid/
      cp "${./firmware/QX2710Patched.bin}" $out/lib/firmware/edid/QX2710Patched.bin
    '')
  ];

  boot.kernelParams = ["quiet" "drm_kms_helper.edid_firmware=DP-3:edid/QX2710Patched.bin"];

This builds fine but when I check the result\firmware directory there is no edid directory. On reboot the EDID file can’t be loaded:

[    0.033829] Kernel command line: initrd=\efi\nixos\iz83zwfddldfkygy992wvyp0v5qp0b1z-initrd-linux-6.1.43-initrd.efi init=/nix/store/a8c33i6l67251xp11i6b23q366rzdqyw-nixos-system-nixos-23.11pre511168.5a8e9243812b/init quiet drm.edid_firmware=DP-3:edid/QX2710Patched.bin splash loglevel=4
[    4.327066] platform DP-3: Direct firmware load for edid/QX2710Patched.bin failed with error -2
[    4.327077] [drm:edid_load [drm]] *ERROR* Requesting EDID firmware "edid/QX2710Patched.bin" failed (err=-2)

Does anyone have any idea on what I’m doing wrong?

Thanks!
Ross

1 Like

UPDATE: I can find the file in /run/current-system/firmware/edid/ but it has a .bin.xz extension.

[ross@nixos:/run/current-system/firmware/edid]$ cd /run/current-system/firmware/edid

[ross@nixos:/run/current-system/firmware/edid]$ ls -la
total 12
dr-xr-xr-x 2 root root 4096 Jan  1  1970 .
dr-xr-xr-x 3 root root 4096 Jan  1  1970 ..
-r--r--r-- 1 root root  272 Jan  1  1970 QX2710Patched.bin.xz

I updated the kernel params with the correct name but still it will not load.

[    0.032613] Kernel command line: initrd=\efi\nixos\arfny9j2qcblwfizjqz5y7g2k5y5xdx5-initrd-linux-6.1.43-initrd.efi init=/nix/store/flanlfzxvpvrpp5s7b417sd4jxmpyssx-nixos-system-nixos-23.11pre511168.5a8e9243812b/init quiet drm.edid_firmware=edid/QX2710Patched.bin.xz splash loglevel=4
[    4.300052] platform DP-3: Direct firmware load for edid/QX2710Patched.bin.xz failed with error -2
[    4.300062] [drm:edid_load [drm]] *ERROR* Requesting EDID firmware "edid/QX2710Patched.bin.xz" failed (err=-2)
1 Like

maybe the extension does not lie and the firmware blob really is gzipped, in which case you’d probably want to gunzip it when copying? EDIT xz’d not gzipped, argh

Thanks :slightly_smiling_face:

I can confirm it is compressed:

[ross@nixos:~]$ xz -d -v QX2710Patched.bin.xz
QX2710Patched.bin.xz (1/1)
  100 %               272 B / 256 B = 1.062 

What I don’t understand is that the original file is not compressed. It seems to me that the firmware is being compressed as part of pkgs.runCommandNoCc?

building '/nix/store/11ya5vxmqgm7bddd7nz1ff1gvdyknr3v-firmware-custom-edid.drv'...
QX2710Patched.bin.xz (1/1)

It seems to me that every file in the firmware folder is xz’d. Is there a way to prevent this for one file?

I’ve found where the compression takes place but I don’t know how to stop it.

How do I override firmware.compressFirmware from the above?

1 Like

This commit adds the compressFirmware option but I’m pretty new to this. Is compressFirmware a variable I can set?

https://github.com/NixOS/nixpkgs/commit/309ea5a1af3b551eb991cfd93580c5f0bc893326

This commit adds compression originally:

https://github.com/NixOS/nixpkgs/commit/8aa8e0ce7f137fe329608efcbb3494a5e6a63f42#diff-e9748219bd7964b622b6f81b741abf43aa8554f8c1edb87b03a13195f4627eae

2 Likes

Awesome progress man, you’ve gotten so close!

The important bit is a little further down the file you linked:

    hardware.firmware = mkOption {
      type = types.listOf types.package;
      default = [];
      description = "[...]";
      apply = list: pkgs.buildEnv {
        name = "firmware";
        paths = map compressFirmware list;
        pathsToLink = [ "/lib/firmware" ];
        ignoreCollisions = true;
      };
};

This is the definition of the option you’re setting in nix.conf. The option is applied by calling pkgs.buildEnv with the list you set the option to (that’s the apply = list: pkgs.buildEnv { part).

In there, you’ll notice paths = map compressFirmware list;. This is what calls the compressFirmware option on your firmware file, which you found to be defined as:

  compressFirmware = firmware: if (config.boot.kernelPackages.kernelAtLeast "5.3" && (firmware.compressFirmware or true)) then
    pkgs.compressFirmwareXz firmware
  else
    id firmware;

The firmware argument in this call is exactly the result of the call in your nix.conf, which is a derivation:

nix-repl> p = pkgs.runCommandNoCC "firmware-custom-edid" {} ''
            mkdir -p $out/lib/firmware/edid/
            cp "${./firmware/QX2710Patched.bin}" $out/lib/firmware/edid/QX2710Patched.bin
          ''
nix-repl> p
«derivation /nix/store/l8ccac39dll1qpfxyqihqwpb91bv7fcv-firmware-custom-edid.drv»

This might be a bit confusing, because the paths argument of buildEnv can also take strings, but when derivations are treated as strings in Nix, they are automatically converted to their output path:

nix-repl> "OutPath: ${p}"
"OutPath: /nix/store/fp0q6cw3vcmp649pd5jf25y3nnb455j2-firmware-custom-edid"

nix-repl> builtins.toString(p)
"/nix/store/fp0q6cw3vcmp649pd5jf25y3nnb455j2-firmware-custom-edid"

Now, a derivation inside the Nix language is just an attribute set, so you could add the compressFirmware attribute like so:

nix-rep> p // { compressFirmware = false; }

But looking at the definition of the arguments of runCommandNoCC, there’s a better option:

pkgs.runCommandNoCC "firmware-custom-edid" { compressFirmware = false; } ''
  mkdir -p $out/lib/firmware/edid/
  cp "${./firmware/QX2710Patched.bin}" $out/lib/firmware/edid/QX2710Patched.bin
''

This adds the compressFirmware attribute to the derivation, and while this is mostly to add environment variables for the script to read, the attribute can also be read directly from the derivation within the Nix language.

3 Likes

Thank you so much - that’s really helpful and it answers so many questions I had :slight_smile:
I am working my way through the language spec to really get my head around this.

One last question on the above: Why does the firmware appear in the /run/current-system/firmware directory but not in the result/firmware directory?

Also I have realised that this isn’t actually what I need - I believe I actually need to copy the firmware into initrd’s /lib/firmware using something like this:

  boot.initrd.extraFiles =  
  { "/lib/firmware/edid/qnix.bin" = 
   { 
     source = "${out}/lib/firmware/edid/qnix.bin";
   }; 
  };  

However although this compiles it does not work. The extra files are copied in stage1.nix where it appears that the source file should be symlinked into the target directory.

However I don’t get any logging to show me why this isn’t working. I have tried using lsinitrd to list the files in the generated initrd and my file is not symlinked (although other firmware files are). Am I missing something simple?

Thanks again for helping me :slight_smile:

    boot.initrd.extraFiles = mkOption {
      default = { };
      type = types.attrsOf
        (types.submodule {
          options = {
            source = mkOption {
              type = types.package;
              description = lib.mdDoc "The object to make available inside the initrd.";
            };
          };
        });
      description = lib.mdDoc ''
        Extra files to link and copy in to the initrd.
      '';
    };
++ (lib.mapAttrsToList
        (symlink: options:
          {
            inherit symlink;
            object = options.source;
          }
        )
        config.boot.initrd.extraFiles);
1 Like

nixos-rebuild doesn’t create a result directory by default. How are you invoking it? Or did you run nix build manually? If so, how did you invoke that?

In my case, the paths are exactly identical.

$ sudo nixos-rebuild switch --flake path:$HOME/.dotfiles
$ readlink /run/current-system
/nix/store/8ba2c0phwcgd3pzi8hdi94r1k8handz1-nixos-system-junction-23.11.20230812.f045184
$ nix build path:$HOME/.dotfiles#nixosConfigurations.junction.config.system.build.toplevel
$ readlink result
/nix/store/8ba2c0phwcgd3pzi8hdi94r1k8handz1-nixos-system-junction-23.11.20230812.f045184

The .config.system.toplevel attribute is what nixos-rebuild builds by default.


About the second question:

What is ${out} referring to here? It could be that this option never checks whether the path actually exists and just silently ignores non-existing files? I would consider that a bug, though.

1 Like

Awesome thanks I will look into the symlink issue further and report back.

I am using nixos-rebuild switch but interestingly my symlinks point to different places. I did switch to unstable a while back and this looks to be the difference. I’m not sure why though :confused:

[ross@nixos:/etc/nixos]$ readlink /run/current-system
/nix/store/10mql9m4icxilb59ybliyawv7kcay0pi-nixos-system-nixos-23.11pre511168.5a8e9243812b

[ross@nixos:/etc/nixos]$ readlink result
/nix/store/mbfvxskrn4ib60rhg88574hf7dd12i4q-nixos-system-nixos-23.05.2162.6da4bc6cb07

Seems like it I am seeing this bug with regards to the result folder: nixos-rebuild pollutes current directory with "result" symlink · Issue #12665 · NixOS/nixpkgs · GitHub.

I’ve cleaned it up now and a nixos-rebuild did not re-create it. That’s helpful because I was looking at the ‘result’ folder for a while.

1 Like

Glad to hear it! Do the contents of run/current-system match what you expect now?

Hey @ross, I was wondering if you were ever able to figure this out? The previous comments on the subject were helpful but I’m ultimately still unable to properly load the firmware.

I wasn’t able to get it working with the initrd approach. It seemed that nixos-rebuild ignored the config change completely as the generated initrd wouldn’t update, even with --install-bootloader.

However, since I didn’t need early init support, I was able to get it working with:
boot.kernelParams = [ drm.edid_firmware=DP-1:edid/edid.bin ];

and

hardware.firmware = [
(
  pkgs.runCommand "edid.bin" { } ''
    mkdir -p $out/lib/firmware/edid
    cp ${../custom-files/edid/edid.bin} $out/lib/firmware/edid/edid.bin
  ''
)];

The first argument can be whatever, and the source for cp is just a reference to the edid in my nixos git repo.

EDIT: Okay so reading the thread more closely, the files in my case actually are compressed too, but it still works. The EDID loads without issue. I am using nixos-unstable so maybe it has been fixed recently?

I’m sorry @snork but I didn’t get it working. Things got busy for me and I had to put this aside for a while. I’ve tried the solution below from @ArticDew but it still fails for me.

@ArticDew can you post the output of dmesg | grep 'edid' for me?
Mine just looks like this:

[    0.000000] Command line: initrd=\efi\nixos\5g1qsvh214l5ws3xb5mc00wg76da4ph8-initrd-linux-6.5.5-initrd.efi init=/nix/store/113x9xfrp1v0gcq0m41ms5pin602wpzf-nixos-system-nixos-23.11pre530560.f5892ddac112/init drm.edid_firmware=edid/edid.bin splash loglevel=4
[    0.026862] Kernel command line: initrd=\efi\nixos\5g1qsvh214l5ws3xb5mc00wg76da4ph8-initrd-linux-6.5.5-initrd.efi init=/nix/store/113x9xfrp1v0gcq0m41ms5pin602wpzf-nixos-system-nixos-23.11pre530560.f5892ddac112/init drm.edid_firmware=edid/edid.bin splash loglevel=4
[    5.112821] amdgpu 0000:07:00.0: Direct firmware load for edid/edid.bin failed with error -2
[    5.112825] amdgpu 0000:07:00.0: [drm] *ERROR* [CONNECTOR:91:DP-1] Requesting EDID firmware "edid/edid.bin" failed (err=-2)

My research makes me think a new initrd image is required. I might try the --install-bootloader parameter shortly.

@ross
I don’t get any output in dmesg when it’s working correctly, but the changes are evident (higher refresh rate and adjusted VRR range).

However, on one of my systems it fails in the same way as you. I thought it was because the edid was broken, but maybe it’s the same issue you’re running into.

The kernel is able to do a direct firmware load of any xz compressed blob, the fact it requests .bin instead of .bin.xz is not the issue.

1 Like

@ross In my case at least the EDID was broken on the secondary system. Once I fixed the EDID, it loaded fine. So I’d make sure your EDID is actually correct.

You can test by writing your edid to /sys/kernel/debug/dri/0/<display id>/edid_override and then unplugging and replugging the cable. You might need to restart your compositor/X11 depending on what you are changing as well.

EDIT: Okay, my bad but I think it actually worked because the cable wasn’t plugged in on boot in this particular instance but later (and I’ve retested to confirm that). I have a weird setup with a DP → HDMI adapter so it could be that it isn’t initialised early enough so the EDID fails. But it could also mean that the original idea was correct and that the system tries to load the EDID before it’s accessible (meaning initrd support would be needed for it to work properly). But this isn’t the case for any of my other displays, although that is on a different system.

Maybe it’s a RDNA2 specific issue (which is honestly full of weird display related quirks).

Looking at the initrd approach again, I can successfully get the EDID to be included and symlinked with this (roughly the same as hardware.firmware usage):

boot.initrd.extraFiles."/lib/firmware/edid/edid.bin".source =
(
  pkgs.runCommand "copy-edids" { } ''
      mkdir -p $out/lib/firmware/edid
      cp ${../custom-files/edid/edid.bin} $out/lib/firmware/edid/edid.bin
    ''
);

Unfortunately, this then breaks firmware loading for other amdgpu modules. So I can’t actually confirm if the EDID loads successfully (other than there are no errors) but obviously it’s not useful anyway as I need that amdgpu firmware. See log:

Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: Direct firmware load for amdgpu/sienna_cichlid_sos.bin failed with error -2
Oct 05 16:53:53 nixgame kernel: [drm:amdgpu_device_init [amdgpu]] *ERROR* early_init of IP block <psp> failed -19
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: Direct firmware load for amdgpu/sienna_cichlid_smc.bin failed with error -2
Oct 05 16:53:53 nixgame kernel: [drm:amdgpu_device_init [amdgpu]] *ERROR* early_init of IP block <smu> failed -19
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: Direct firmware load for amdgpu/sienna_cichlid_dmcub.bin failed with error -2
Oct 05 16:53:53 nixgame kernel: [drm:dm_early_init [amdgpu]] *ERROR* DMUB firmware loading failed: -19
Oct 05 16:53:53 nixgame kernel: [drm:amdgpu_device_init [amdgpu]] *ERROR* early_init of IP block <dm> failed -19
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: Direct firmware load for amdgpu/sienna_cichlid_pfp.bin failed with error -2
Oct 05 16:53:53 nixgame kernel: [drm:amdgpu_device_init [amdgpu]] *ERROR* early_init of IP block <gfx_v10_0> failed -19
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: Direct firmware load for amdgpu/sienna_cichlid_vcn.bin failed with error -2
Oct 05 16:53:53 nixgame kernel: [drm:amdgpu_device_init [amdgpu]] *ERROR* early_init of IP block <vcn_v3_0> failed -19
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: amdgpu: Fatal error during GPU init
Oct 05 16:53:53 nixgame kernel: amdgpu 0000:09:00.0: amdgpu: amdgpu: finishing device.

For the record, this doesn’t compile at all for me:

boot.initrd.extraFiles =  
  { "/lib/firmware/edid/edid.bin" = 
   { 
     source = "${out}/lib/firmware/edid/edid.bin";
   }; 
  };