#!/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