nixos/hardware.display: init

This commit is contained in:
Krzysztof Nazarewski 2024-01-22 11:46:33 +01:00 committed by K900
parent 8559b460b1
commit 676a51c41f
4 changed files with 327 additions and 0 deletions

View File

@ -13,6 +13,9 @@
- `authelia` has been upgraded to version 4.38. This version brings several features and improvements which are detailed in the [release blog post](https://www.authelia.com/blog/4.38-release-notes/).
This release also deprecates some configuration keys, which are likely to be removed in future version 5.0, but they are still supported and expected to be working in the current version.
- `hardware.display` is a new module implementing workarounds for misbehaving monitors
through setting up custom EDID files and forcing kernel/framebuffer modes.
## New Services {#sec-release-24.11-new-services}
- [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI

View File

@ -567,6 +567,7 @@
./services/hardware/bolt.nix
./services/hardware/brltty.nix
./services/hardware/ddccontrol.nix
./services/hardware/display.nix
./services/hardware/fancontrol.nix
./services/hardware/freefall.nix
./services/hardware/fwupd.nix

View File

@ -0,0 +1,130 @@
# Customizing display configuration {#module-hardware-display}
This section describes how to customize display configuration using:
- kernel modes
- EDID files
Example situations it can help you with:
- display controllers (external hardware) not advertising EDID at all,
- misbehaving graphics drivers,
- loading custom display configuration before the Display Manager is running,
## Forcing display modes {#module-hardware-display-modes}
In case of very wrong monitor controller and/or video driver combination you can
[force the display to be enabled](https://mjmwired.net/kernel/Documentation/fb/modedb.txt#41)
and skip some driver-side checks by adding `video=<OUTPUT>:e` to `boot.kernelParams`.
This is exactly the case with [`amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392)
```nix
{
# force enabled output to skip `amdgpu` checks
hardware.display.outputs."DP-1".mode = "e";
# completely disable output no matter what is connected to it
hardware.display.outputs."VGA-2".mode = "d";
/* equals
boot.kernelParams = [ "video=DP-1:e" "video=VGA-2:d" ];
*/
}
```
## Crafting custom EDID files {#module-hardware-display-edid-custom}
To make custom EDID binaries discoverable you should first create a derivation storing them at
`$out/lib/firmware/edid/` and secondly add that derivation to `hardware.display.edid.packages` NixOS option:
```nix
{
hardware.display.edid.packages = [
(pkgs.runCommand "edid-custom" {} ''
mkdir -p $out/lib/firmware/edid
base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
EOF
base64 -d > "$out/lib/firmware/edid/custom2.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card1-.../edid`>
EOF
'')
];
}
```
There are 2 options significantly easing preparation of EDID files:
- `hardware.display.edid.linuxhw`
- `hardware.display.edid.modelines`
## Assigning EDID files to displays {#module-hardware-display-edid-assign}
To assign available custom EDID binaries to your monitor (video output) use `hardware.display.outputs."<NAME>".edid` option.
Under the hood it adds `drm.edid_firmware` entry to `boot.kernelParams` NixOS option for each configured output:
```nix
{
hardware.display.outputs."VGA-1".edid = "custom1.bin";
hardware.display.outputs."VGA-2".edid = "custom2.bin";
/* equals:
boot.kernelParams = [ "drm.edid_firmware=VGA-1:edid/custom1.bin,VGA-2:edid/custom2.bin" ];
*/
}
```
## Pulling files from linuxhw/EDID database {#module-hardware-display-edid-linuxhw}
`hardware.display.edid.linuxhw` utilizes `pkgs.linuxhw-edid-fetcher` to extract EDID files
from https://github.com/linuxhw/EDID based on simple string/regexp search identifying exact entries:
```nix
{
hardware.display.edid.linuxhw."PG278Q_2014" = [ "PG278Q" "2014" ];
/* equals:
hardware.display.edid.packages = [
(pkgs.linuxhw-edid-fetcher.override {
displays = {
"PG278Q_2014" = [ "PG278Q" "2014" ];
};
})
];
*/
}
```
## Using XFree86 Modeline definitions {#module-hardware-display-edid-modelines}
`hardware.display.edid.modelines` utilizes `pkgs.edid-generator` package allowing you to
conveniently use [`XFree86 Modeline`](https://en.wikipedia.org/wiki/XFree86_Modeline) entries as EDID binaries:
```nix
{
hardware.display.edid.modelines."PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
hardware.display.edid.modelines."PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
/* equals:
hardware.display.edid.packages = [
(pkgs.edid-generator.overrideAttrs {
clean = true;
modelines = ''
Modeline "PG278Q_60" 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync
Modeline "PG278Q_120" 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync
'';
})
];
*/
}
```
## Complete example for Asus PG278Q {#module-hardware-display-pg278q}
And finally this is a complete working example for a 2014 (first) batch of [Asus PG278Q monitor with `amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392):
```nix
{
hardware.display.edid.modelines."PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
hardware.display.edid.modelines."PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
hardware.display.outputs."DP-1".edid = "PG278Q_60.bin";
hardware.display.outputs."DP-1".mode = "e";
}
```

View File

@ -0,0 +1,193 @@
{ config, lib, pkgs, ... }:
let
cfg = config.hardware.display;
in
{
meta.doc = ./display.md;
meta.maintainers = with lib.maintainers; [
nazarewk
];
options = {
hardware.display.edid.enable = lib.mkOption {
type = with lib.types; bool;
default = cfg.edid.packages != null;
defaultText = lib.literalExpression "config.hardware.display.edid.packages != null";
description = ''
Enables handling of EDID files
'';
};
hardware.display.edid.packages = lib.mkOption {
type = with lib.types; listOf package;
default = [ ];
description = ''
List of packages containing EDID binary files at `$out/lib/firmware/edid`.
Such files will be available for use in `drm.edid_firmware` kernel
parameter as `edid/<filename>`.
You can craft one directly here or use sibling options `linuxhw` and `modelines`.
'';
example = lib.literalExpression ''
[
(pkgs.runCommand "edid-custom" {} '''
mkdir -p "$out/lib/firmware/edid"
base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
<insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
EOF
''')
]
'';
apply = list:
if list == [ ] then null else
(pkgs.buildEnv {
name = "firmware-edid";
paths = list;
pathsToLink = [ "/lib/firmware/edid" ];
ignoreCollisions = true;
}) // {
compressFirmware = false;
};
};
hardware.display.edid.linuxhw = lib.mkOption {
type = with lib.types; attrsOf (listOf str);
default = { };
description = ''
Exposes EDID files from users-sourced database at https://github.com/linuxhw/EDID
Attribute names will be mapped to EDID filenames `<NAME>.bin`.
Attribute values are lists of `awk` regexp patterns that (together) must match
exactly one line in either of:
- [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md)
- [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md)
There is no universal way of locating your device config, but here are some practical tips:
1. locate your device:
- find your model number (second column)
- locate manufacturer (first column) and go through the list manually
2. narrow down results using other columns until there is only one left:
- `Name` column
- production date (`Made` column)
- resolution `Res`
- screen diagonal (`Inch` column)
- as a last resort use `ID` from the last column
'';
example = lib.literalExpression ''
{
PG278Q_2014 = [ "PG278Q" "2014" ];
}
'';
apply = displays:
if displays == { } then null else
pkgs.linuxhw-edid-fetcher.override { inherit displays; };
};
hardware.display.edid.modelines = lib.mkOption {
type = with lib.types; attrsOf str;
default = { };
description = ''
Attribute set of XFree86 Modelines automatically converted
and exposed as `edid/<name>.bin` files in initrd.
See for more information:
- https://en.wikipedia.org/wiki/XFree86_Modeline
'';
example = lib.literalExpression ''
{
"PG278Q_60" = " 241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
"PG278Q_120" = " 497.75 2560 2608 2640 2720 1440 1443 1448 1525 +hsync -vsync";
"U2711_60" = " 241.50 2560 2600 2632 2720 1440 1443 1448 1481 -hsync +vsync";
}
'';
apply = modelines:
if modelines == { } then null else
pkgs.edid-generator.overrideAttrs {
clean = true;
passthru.config = modelines;
modelines = lib.trivial.pipe modelines [
(lib.mapAttrsToList (name: value:
lib.throwIfNot (builtins.stringLength name <= 12) "Modeline name must be 12 characters or less"
''Modeline "${name}" ${value}''
))
(builtins.map (line: "${line}\n"))
(lib.strings.concatStringsSep "")
];
};
};
hardware.display.outputs = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({
options = {
edid = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
An EDID filename to be used for configured display, as in `edid/<filename>`.
See for more information:
- `hardware.display.edid.packages`
- https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID
'';
};
mode = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
A `video` kernel parameter (framebuffer mode) configuration for the specific output:
<xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
See for more information:
- https://docs.kernel.org/fb/modedb.html
- https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes
'';
example = lib.literalExpression ''
"e"
'';
};
};
}));
description = ''
Hardware/kernel-level configuration of specific outputs.
'';
default = { };
example = lib.literalExpression ''
{
edid.modelines."PG278Q_60" = "241.50 2560 2608 2640 2720 1440 1443 1448 1481 -hsync +vsync";
outputs."DP-1".edid = "PG278Q_60.bin";
outputs."DP-1".mode = "e";
}
'';
};
};
config = lib.mkMerge [
{
hardware.display.edid.packages =
lib.optional (cfg.edid.modelines != null) cfg.edid.modelines
++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw;
boot.kernelParams =
# forcing video modes
lib.trivial.pipe cfg.outputs [
(lib.attrsets.filterAttrs (_: spec: spec.mode != null))
(lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}"))
]
++
# selecting EDID for displays
lib.trivial.pipe cfg.outputs [
(lib.attrsets.filterAttrs (_: spec: spec.edid != null))
(lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}"))
(builtins.concatStringsSep ",")
(p: lib.optional (p != "") "drm.edid_firmware=${p}")
]
;
}
(lib.mkIf (cfg.edid.packages != null) {
# services.udev implements hardware.firmware option
services.udev.enable = true;
hardware.firmware = [ cfg.edid.packages ];
})
];
}