Merge pull request #279789 from nazarewk/hardware-firmware-edid-fix
nixos/hardware.display: init module
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
```
|
||||
@@ -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 ];
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -89,6 +89,14 @@ for module in $(< ~-/closure); do
|
||||
done || :
|
||||
done
|
||||
|
||||
if test -e lib/firmware/edid ; then
|
||||
echo "lib/firmware/edid found, copying."
|
||||
mkdir -p "$out/lib/firmware"
|
||||
cp -v --no-preserve=mode --recursive --dereference --no-target-directory lib/firmware/edid "$out/lib/firmware/edid"
|
||||
else
|
||||
echo "lib/firmware/edid not found, skipping."
|
||||
fi
|
||||
|
||||
# copy module ordering hints for depmod
|
||||
cp $kernel/lib/modules/"$version"/modules.order $out/lib/modules/"$version"/.
|
||||
cp $kernel/lib/modules/"$version"/modules.builtin $out/lib/modules/"$version"/.
|
||||
|
||||
Executable
+176
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEuo pipefail
|
||||
test -z "${DEBUG:-}" || set -x
|
||||
set -eEuo pipefail
|
||||
|
||||
FIRMWARE_PATH="${EDID_PATH:-"/run/current-system/firmware"}"
|
||||
mapfile -t edid_paths <<<"${FIRMWARE_PATH//":"/$'\n'}"
|
||||
|
||||
err() {
|
||||
LOGGER="ERROR" log "$@"
|
||||
return 1
|
||||
}
|
||||
|
||||
log() {
|
||||
# shellcheck disable=SC2059
|
||||
printf "[${LOGGER:-"INFO"}] $1\n" "${@:2}" >&2
|
||||
}
|
||||
|
||||
find_path() {
|
||||
local filePath="$1"
|
||||
mapfile -t candidates < <(
|
||||
set -x
|
||||
find -L "${@:2}" -path "*/${filePath}"
|
||||
)
|
||||
if test "${#candidates[@]}" -eq 0; then
|
||||
log "'%s' path not found" "${filePath}"
|
||||
return 1
|
||||
fi
|
||||
log "'%s' path found at %s" "${filePath}" "${candidates[0]}"
|
||||
echo -n "${candidates[0]}"
|
||||
}
|
||||
|
||||
wait_for_file() {
|
||||
local filePath="$1"
|
||||
until find_path "${filePath}" "${@:2}"; do
|
||||
backoff "${filePath}"
|
||||
done
|
||||
}
|
||||
|
||||
backoff() {
|
||||
local what="$1" sleepFor
|
||||
|
||||
backoff_start="${backoff_start:-"5"}"
|
||||
backoff_current="${backoff_current:-"${backoff_start}"}"
|
||||
backoff_jitter_multiplier="${backoff_jitter_multiplier:-"0.3"}"
|
||||
backoff_multiplier="${backoff_multiplier:-1.5}"
|
||||
|
||||
sleepFor="$(bc <<<"${backoff_current} + ${RANDOM} % (${backoff_current} * ${backoff_jitter_multiplier})")"
|
||||
|
||||
log "still waiting for '%s', retry in %s sec..." "${what}" "${sleepFor}"
|
||||
sleep "${sleepFor}"
|
||||
backoff_current="$(bc <<<"scale=2; ${backoff_current} * ${backoff_multiplier}")"
|
||||
|
||||
}
|
||||
|
||||
force_mode() {
|
||||
local connPath="$1" newMode="$2" currentMode
|
||||
currentMode="$(cat "$connPath/force")"
|
||||
if test "${currentMode}" == "${newMode}"; then
|
||||
log "video mode is already '%s'" "${currentMode}"
|
||||
return
|
||||
fi
|
||||
log "changing video mode from '%s' to '%s'" "${currentMode}" "${newMode}"
|
||||
echo "${newMode}" >"$connPath/force"
|
||||
CHANGED=1
|
||||
}
|
||||
|
||||
force_edid() {
|
||||
local connPath="$1" edidPath="$2"
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
local connPath="$1" mode="$2"
|
||||
test -n "$mode" || return
|
||||
log "setting up fb mode..."
|
||||
|
||||
# see https://github.com/torvalds/linux/blob/8cd26fd90c1ad7acdcfb9f69ca99d13aa7b24561/drivers/gpu/drm/drm_sysfs.c#L202-L207
|
||||
# see https://docs.kernel.org/fb/modedb.html
|
||||
case "${mode}" in
|
||||
*d) force_mode "$connPath" off ;;
|
||||
*e) force_mode "$connPath" on ;;
|
||||
*D) force_mode "$connPath" on-digital ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_edid() {
|
||||
local connPath="$1" edidFilename="$2" edidPath
|
||||
test -n "${edidFilename}" || return
|
||||
log "loading EDID override..."
|
||||
edidPath="$(find_path "${edidFilename}" "${edid_paths[@]/%/"/"}" -maxdepth 2)"
|
||||
|
||||
force_edid "${connPath}" "$edidPath"
|
||||
cat "$edidPath" >"${connPath}/edid_override"
|
||||
|
||||
if cmp "${connPath}/edid_override" "${edidPath}" &>/dev/null; then
|
||||
log "EDID is already up to date with '%s'" "${edidPath}"
|
||||
else
|
||||
log "applying EDID override from ${edidPath}"
|
||||
cat "$edidPath" >"${connPath}/edid_override"
|
||||
CHANGED=1
|
||||
fi
|
||||
}
|
||||
|
||||
load() {
|
||||
local conn="$1" edidFilename="$2" mode="$3"
|
||||
export LOGGER="$conn:${edidFilename}:$mode"
|
||||
CHANGED="${CHANGED:-0}"
|
||||
|
||||
log "starting configuration"
|
||||
local connPath
|
||||
connPath="$(wait_for_file "$conn" /sys/kernel/debug/dri/ -maxdepth 2 -type d)"
|
||||
apply_edid "${connPath}" "${edidFilename}"
|
||||
apply_mode "${connPath}" "$mode"
|
||||
|
||||
if test "${CHANGED}" != 0; then
|
||||
log "changes detected, triggering hotplug"
|
||||
echo 1 >"${connPath}/trigger_hotplug"
|
||||
else
|
||||
log "no changes detected, skipping hotplug trigger"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
err "must be run as root"
|
||||
fi
|
||||
|
||||
if test "$#" == 0; then
|
||||
log "loading kernel parameters from /proc/cmdline"
|
||||
# replace script arguments with kernel parameters
|
||||
mapfile -t args < <(xargs -n1 </proc/cmdline)
|
||||
else
|
||||
log "loading kernel parameters compatible arguments from commandline"
|
||||
args=("$@")
|
||||
fi
|
||||
|
||||
local -A edids modes connectors
|
||||
local -a entries
|
||||
local key value
|
||||
|
||||
for arg in "${args[@]}"; do
|
||||
key="${arg%%=*}"
|
||||
value=""
|
||||
test "${key}" == "${arg}" || value="${arg#*=}"
|
||||
|
||||
case "${key}" in
|
||||
video)
|
||||
# one argument per connector:
|
||||
# video=DP-4:e video=DP-1:e
|
||||
connector="${value%:*}"
|
||||
mode="${value#*:}"
|
||||
connectors["${connector}"]=""
|
||||
modes["$connector"]="$mode"
|
||||
;;
|
||||
drm.edid_firmware)
|
||||
# single argument for all connectors:
|
||||
# drm.edid_firmware=DP-4:edid/one.bin,DP-1:edid/two.bin
|
||||
mapfile -t entries <<<"${value//","/$'\n'}"
|
||||
for entry in "${entries[@]}"; do
|
||||
connector="${entry%:*}"
|
||||
edidFilename="${entry#*:}"
|
||||
connectors["${connector}"]=""
|
||||
edids["${connector}"]="${edidFilename}"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
for connector in "${!connectors[@]}"; do
|
||||
# spawn in a subshell to easily adjust and runtime modify global variables
|
||||
(load "${connector}" "${edids["${connector}"]:-""}" "${modes["${connector}"]:-""}") &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,19 @@
|
||||
{ lib
|
||||
, writeShellApplication
|
||||
, bc
|
||||
, diffutils
|
||||
, findutils
|
||||
, coreutils
|
||||
, firmwarePaths ? [
|
||||
"/run/current-system/firmware"
|
||||
]
|
||||
}:
|
||||
writeShellApplication {
|
||||
name = "edido";
|
||||
meta.description = "A tool to apply display configuration from `boot.kernelParams`.";
|
||||
runtimeInputs = [ diffutils findutils coreutils bc ];
|
||||
text = ''
|
||||
FIRMWARE_PATH="''${FIRMWARE_PATH:-"${builtins.concatStringsSep ":" firmwarePaths}"}"
|
||||
${builtins.readFile ./edido.sh}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eEuo pipefail
|
||||
test -z "${DEBUG:-}" || set -x
|
||||
# based on instructions provided in https://github.com/linuxhw/EDID/blob/98bc7d6e2c0eaad61346a8bf877b562fee16efc3/README.md
|
||||
|
||||
usage() {
|
||||
cat <<EOF >&2
|
||||
Usage:
|
||||
${BASH_SOURCE[0]} PG278Q 2014 >edid.bin
|
||||
repo=/path/to/linuxhw/EDID ${BASH_SOURCE[0]} PG278Q 2014 >edid.bin
|
||||
|
||||
verify the generated file:
|
||||
edid-decode <edid.bin
|
||||
parse-edid <edid.bin
|
||||
|
||||
load the generated file:
|
||||
cat edid.bin >/sys/kernel/debug/dri/0/DP-1/edid_override
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
# shellcheck disable=SC2059
|
||||
printf "${1}\n" "${@:2}" >&2
|
||||
}
|
||||
|
||||
find_displays() {
|
||||
local script=("BEGIN { IGNORECASE=1 } /${1}/")
|
||||
|
||||
for pattern in "${@:2}"; do
|
||||
script+=('&&' "/${pattern}/")
|
||||
done
|
||||
cat "${repo}"/{Analog,Digital}Display.md | awk "${script[*]}"
|
||||
}
|
||||
|
||||
to_edid() {
|
||||
if ! test -e "$1"; then
|
||||
log "EDID specification file $1 does not exist,"
|
||||
log "it is most likely an error with https://github.com/linuxhw/EDID"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Extracting $1..."
|
||||
|
||||
# https://github.com/linuxhw/EDID/blob/228fea5d89782402dd7f84a459df7f5248573b10/README.md#L42-L42
|
||||
grep -E '^([a-f0-9]{32}|[a-f0-9 ]{47})$' <"$1" | tr -d '[:space:]' | xxd -r -p
|
||||
}
|
||||
|
||||
extract_link() {
|
||||
awk '{ gsub(/^.+]\(</, ""); gsub(/>).+/, ""); print }'
|
||||
}
|
||||
|
||||
check_repo() {
|
||||
test -d "$1" && test -f "$1/AnalogDisplay.md" && test -f "$1/DigitalDisplay.md"
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ $# == 0 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${repo:="$PWD"}"
|
||||
|
||||
if ! check_repo "$repo"; then
|
||||
repo="${TMPDIR:-/tmp}/edid"
|
||||
log "Not running inside 'https://github.com/linuxhw/EDID', downloading content to ${repo}"
|
||||
if ! check_repo "$repo"; then
|
||||
curl -L https://github.com/linuxhw/EDID/tarball/HEAD | tar -zx -C "${repo}" --strip-components=1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Using repository at ${repo}"
|
||||
|
||||
readarray -t lines < <(find_displays "${@}")
|
||||
case "${#lines[@]}" in
|
||||
0)
|
||||
log "No matches, try broader patterns?"
|
||||
exit 1
|
||||
;;
|
||||
1)
|
||||
log "Matched entries:"
|
||||
log "> %s" "${lines[@]}"
|
||||
log "Found exactly one pattern, continuing..."
|
||||
;;
|
||||
*)
|
||||
log "Matched entries:"
|
||||
log "> %s" "${lines[@]}"
|
||||
log "More than one match, make patterns more specific until there is only one left"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
to_edid "${repo}/$(extract_link <<<"${lines[0]}")"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,66 @@
|
||||
{ lib
|
||||
, coreutils
|
||||
, curl
|
||||
, fetchFromGitHub
|
||||
, gawk
|
||||
, gnutar
|
||||
, stdenv
|
||||
, unixtools
|
||||
, writeShellApplication
|
||||
, nix-update-script
|
||||
, displays ? { }
|
||||
}:
|
||||
|
||||
# Usage:
|
||||
# let
|
||||
# edids = linuxhw-edid-fetcher.override {
|
||||
# displays.PG278Q_2014 = [ "PG278Q" "2560x1440" "2014" ];
|
||||
# };
|
||||
# in
|
||||
# "${edids}/lib/firmware/edid/PG278Q_2014.bin";
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "linuxhw-edid-fetcher";
|
||||
version = "unstable-2023-05-08";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "linuxhw";
|
||||
repo = "EDID";
|
||||
rev = "98bc7d6e2c0eaad61346a8bf877b562fee16efc3";
|
||||
hash = "sha256-+Vz5GU2gGv4QlKO4A6BlKSETxE5GAcehKZL7SEbglGE=";
|
||||
};
|
||||
|
||||
fetch = lib.getExe (writeShellApplication {
|
||||
name = "linuxhw-edid-fetch";
|
||||
runtimeInputs = [ gawk coreutils unixtools.xxd curl gnutar ];
|
||||
text = ''
|
||||
repo="''${repo:-"${src}"}"
|
||||
${builtins.readFile ./linuxhw-edid-fetch.sh}
|
||||
'';
|
||||
});
|
||||
|
||||
configurePhase = lib.pipe displays [
|
||||
(lib.mapAttrsToList (name: patterns: ''
|
||||
"$fetch" ${lib.escapeShellArgs patterns} > "${name}.bin"
|
||||
''))
|
||||
(builtins.concatStringsSep "\n")
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p "$out/bin"
|
||||
ln -s "$fetch" "$out/bin/"
|
||||
${lib.optionalString (displays != { }) ''
|
||||
install -D --mode=444 --target-directory="$out/lib/firmware/edid" *.bin
|
||||
''}
|
||||
'';
|
||||
|
||||
passthru.updateScript = nix-update-script { extraArgs = [ "--version=branch=master" ]; };
|
||||
|
||||
meta = {
|
||||
description = "Fetcher for EDID binaries from Linux Hardware Project's EDID repository";
|
||||
homepage = "https://github.com/linuxhw/EDID";
|
||||
license = lib.licenses.cc-by-40;
|
||||
maintainers = with lib.maintainers; [ nazarewk ];
|
||||
platforms = lib.platforms.all;
|
||||
mainProgram = "linuxhw-edid-fetch";
|
||||
};
|
||||
}
|
||||
@@ -1147,6 +1147,8 @@ let
|
||||
# For systemd-binfmt
|
||||
BINFMT_MISC = option yes;
|
||||
|
||||
# Required for EDID overriding
|
||||
FW_LOADER = yes;
|
||||
# Disable the firmware helper fallback, udev doesn't implement it any more
|
||||
FW_LOADER_USER_HELPER_FALLBACK = option no;
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ let
|
||||
|
||||
config = {
|
||||
CONFIG_MODULES = "y";
|
||||
CONFIG_FW_LOADER = "m";
|
||||
CONFIG_FW_LOADER = "y";
|
||||
CONFIG_RUST = if withRust then "y" else "n";
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user