summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrzysztof Nazarewski <gpg@kdn.im>2024-04-18 13:33:19 +0200
committerK900 <me@0upti.me>2024-07-18 16:57:18 +0300
commit8559b460b1caf1b619ef25ef9bb251cfa8282db9 (patch)
tree232072c21158184a99cbe72c41298c00c2437413
parentlinuxhw-edid-fetcher: init at unstable-2023-05-08 (diff)
downloadnixpkgs-8559b460b1caf1b619ef25ef9bb251cfa8282db9.tar.gz
edido: init
-rwxr-xr-xpkgs/by-name/ed/edido/edido.sh176
-rw-r--r--pkgs/by-name/ed/edido/package.nix19
2 files changed, 195 insertions, 0 deletions
diff --git a/pkgs/by-name/ed/edido/edido.sh b/pkgs/by-name/ed/edido/edido.sh
new file mode 100755
index 000000000000..136205386ee1
--- /dev/null
+++ b/pkgs/by-name/ed/edido/edido.sh
@@ -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 "$@"
diff --git a/pkgs/by-name/ed/edido/package.nix b/pkgs/by-name/ed/edido/package.nix
new file mode 100644
index 000000000000..2d1f69e6a740
--- /dev/null
+++ b/pkgs/by-name/ed/edido/package.nix
@@ -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}
+ '';
+}