176 lines
4.6 KiB
Bash
Executable file
176 lines
4.6 KiB
Bash
Executable file
#!/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 "$@"
|