diff --git a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh index e161e2cb..16ab2a32 100755 --- a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh +++ b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh @@ -33,6 +33,8 @@ fi # shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_display.sh" TESTNAME="weston-simple-egl" RES_FILE="./${TESTNAME}.res" @@ -78,7 +80,19 @@ log_info "Config: DURATION=${DURATION} STOP_GRACE=${STOP_GRACE} EXPECT_FPS=${EXP if command -v display_debug_snapshot >/dev/null 2>&1; then display_debug_snapshot "pre-display-check" fi - + +# Always print modetest as part of the snapshot (best-effort). +# (Some lib_display.sh variants override display_debug_snapshot without modetest.) +if command -v modetest >/dev/null 2>&1; then + log_info "----- modetest -M msm -ac (capped at 200 lines) -----" + modetest -M msm -ac 2>&1 | sed -n '1,200p' | while IFS= read -r l; do + [ -n "$l" ] && log_info "[modetest] $l" + done + log_info "----- End modetest -M msm -ac -----" +else + log_warn "modetest not found in PATH skipping modetest snapshot." +fi + have_connector=0 if command -v display_connected_summary >/dev/null 2>&1; then sysfs_summary=$(display_connected_summary) @@ -87,13 +101,12 @@ if command -v display_connected_summary >/dev/null 2>&1; then log_info "Connected display (sysfs): $sysfs_summary" fi fi - + if [ "$have_connector" -eq 0 ]; then log_warn "No connected DRM display found, skipping ${TESTNAME}." echo "${TESTNAME} SKIP" >"$RES_FILE" exit 0 fi - # --------------------------------------------------------------------------- # Wayland / Weston environment (runtime detection, no hardcoded flavour) # --------------------------------------------------------------------------- @@ -103,7 +116,7 @@ fi sock="" -# 1) Try to find any existing Wayland socket (base or overlay) +# Try to find any existing Wayland socket (base or overlay) if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then sock=$(discover_wayland_socket_anywhere | head -n 1 || true) fi @@ -116,9 +129,13 @@ if [ -n "$sock" ] && command -v adopt_wayland_env_from_socket >/dev/null 2>&1; t fi fi -# 2) If no usable socket yet, try starting a private Weston (overlay-style helper) +# If no usable socket yet, try starting a private Weston (overlay-style helper) if [ -z "$sock" ] && command -v overlay_start_weston_drm >/dev/null 2>&1; then log_info "No usable Wayland socket; trying overlay_start_weston_drm helper..." + if command -v weston_force_primary_1080p60_if_not_60 >/dev/null 2>&1; then + log_info "Pre-configuring primary output to ~60Hz before starting Weston (best-effort) ..." + weston_force_primary_1080p60_if_not_60 || true + fi if overlay_start_weston_drm; then # Re-scan for a socket after attempting to start Weston if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then @@ -137,7 +154,7 @@ if [ -z "$sock" ] && command -v overlay_start_weston_drm >/dev/null 2>&1; then fi fi -# 3) Final decision: run or SKIP +# Final decision: run or SKIP if [ -z "$sock" ]; then log_warn "No Wayland socket found after autodetection; skipping ${TESTNAME}." echo "${TESTNAME} SKIP" >"$RES_FILE" @@ -152,7 +169,35 @@ if command -v wayland_connection_ok >/dev/null 2>&1; then fi log_info "Wayland connection test: OK" else - log_warn "wayland_connection_ok helper not found; continuing without explicit Wayland probe." + log_warn "wayland_connection_ok helper not found continuing without explicit Wayland probe." +fi + +# --------------------------------------------------------------------------- +# Ensure primary output is ~60Hz (best-effort, no churn if already ~60Hz) +# - Must NOT change weston.ini behavior: that remains inside lib_display.sh +# - First checks current mode (modetest path) or config (weston.ini path) +# - Only attempts changes when NOT ~60Hz +# --------------------------------------------------------------------------- +if command -v display_debug_snapshot >/dev/null 2>&1; then + display_debug_snapshot "${TESTNAME}: before-ensure-60hz" +fi +if command -v wayland_debug_snapshot >/dev/null 2>&1; then + wayland_debug_snapshot "${TESTNAME}: before-ensure-60hz" +fi + +if command -v weston_force_primary_1080p60_if_not_60 >/dev/null 2>&1; then + log_info "Ensuring primary output is ~60Hz (best-effort) ..." + if weston_force_primary_1080p60_if_not_60; then + log_info "Primary output is ~60Hz (or was already ~60Hz)." + else + log_warn "Unable to force ~60Hz (continuing; not a hard failure)." + fi +else + log_warn "weston_force_primary_1080p60_if_not_60 helper not found; skipping ~60Hz enforcement." +fi + +if command -v display_debug_snapshot >/dev/null 2>&1; then + display_debug_snapshot "${TESTNAME}: after-ensure-60hz" fi # --------------------------------------------------------------------------- diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index f1af00d6..ffba0e62 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -1303,28 +1303,41 @@ ensure_wayland_runtime_dir_perms() { # Prefers `wayland-info` with a short timeout; otherwise validates socket presence. # Also enforces/fixes XDG_RUNTIME_DIR permissions so clients won’t reject it. wayland_connection_ok() { + sock="" + # Sanity-check the socket path first. if [ -z "$XDG_RUNTIME_DIR" ] || [ -z "$WAYLAND_DISPLAY" ]; then log_warn "wayland_connection_ok: XDG_RUNTIME_DIR or WAYLAND_DISPLAY not set" - elif [ ! -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then - log_warn "wayland_connection_ok: no Wayland socket at $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" - return 1 else - log_info "wayland_connection_ok: using socket $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + sock="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + if [ ! -S "$sock" ]; then + log_warn "wayland_connection_ok: no Wayland socket at $sock" + return 1 + fi + log_info "wayland_connection_ok: using socket $sock" fi - + if command -v wayland-info >/dev/null 2>&1; then log_info "Probing Wayland with: wayland-info" - wayland-info >/dev/null 2>&1 && return 0 + wayland-info >/dev/null 2>&1 + rc=$? + [ "$rc" -eq 0 ] && return 0 + # Accept common “killed by timeout/signal” cases as OK (best-effort probe) + [ "$rc" -eq 143 ] && return 0 + [ "$rc" -eq 124 ] && return 0 return 1 fi - + if command -v weston-info >/dev/null 2>&1; then log_info "Probing Wayland with: weston-info" - weston-info >/dev/null 2>&1 && return 0 + weston-info >/dev/null 2>&1 + rc=$? + [ "$rc" -eq 0 ] && return 0 + [ "$rc" -eq 143 ] && return 0 + [ "$rc" -eq 124 ] && return 0 return 1 fi - + if command -v weston-simple-egl >/dev/null 2>&1; then log_info "Probing Wayland by briefly starting weston-simple-egl" ( @@ -1333,27 +1346,26 @@ wayland_connection_ok() { ) pid="$(cat "/tmp/.wsegl.$$" 2>/dev/null || echo '')" rm -f "/tmp/.wsegl.$$" 2>/dev/null || true - + i=0 while [ "$i" -lt 2 ]; do sleep 1 i=$((i + 1)) done - + if [ -n "$pid" ]; then kill "$pid" 2>/dev/null || true fi # If it started at all, consider the connection OK (best effort). return 0 fi - + # Last resort: trust socket existence alone. - if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && - [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + if [ -n "$sock" ] && [ -S "$sock" ]; then log_info "No probe tools present; accepting socket existence as OK." return 0 fi - + return 1 } # Very verbose snapshot for debugging (processes, sockets, env, perms). @@ -1422,6 +1434,63 @@ print_path_meta() { ############################################################################### # DRM / Display helpers (portable, minimal-build friendly) ############################################################################### +# Best-effort: return current mode for a DRM connector using debugfs state. +# Input : full connector name like "card0-HDMI-A-1" +# Output: "1920x1080@60.00" (or "1920x1080@60") or "-" if unknown/unavailable +display_connector_cur_mode() { + full="$1" + [ -n "$full" ] || { echo "-"; return; } + + card="${full%%-*}" # card0 + con="${full#*-}" # HDMI-A-1 + idx="${card#card}" # 0 + + state="/sys/kernel/debug/dri/${idx}/state" + + # debugfs may not be mounted; best-effort mount (ignore failures) + if [ ! -r "$state" ] && [ -r /proc/mounts ] && command -v mount >/dev/null 2>&1; then + if ! grep -q " /sys/kernel/debug " /proc/mounts 2>/dev/null; then + mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true + fi + fi + + [ -r "$state" ] || { echo "-"; return; } + + awk -v CON="$con" ' + BEGIN { crtc=""; in_conn=0; in_crtc=0; mode=""; vr=""; active="" } + + # Connector block -> capture bound CRTC id + $0 ~ /^connector/ { + in_conn=0 + if ($0 ~ CON) in_conn=1 + } + in_conn && $0 ~ /crtc/ { + if (match($0, /[0-9]+/, m)) crtc=m[0] + } + in_conn && $0 ~ /^$/ { in_conn=0 } + + # CRTC block -> capture active + mode + refresh + crtc != "" && $0 ~ /^crtc/ { + in_crtc=0 + if ($0 ~ ("crtc " crtc)) in_crtc=1 + } + in_crtc && $0 ~ /active/ { + if ($0 ~ /(yes|true|1)/) active="1" + } + in_crtc && $0 ~ /mode/ { + if (match($0, /[0-9]+x[0-9]+/, m)) mode=m[0] + } + in_crtc && ($0 ~ /vrefresh/ || $0 ~ /refresh/) { + if (match($0, /[0-9]+(\.[0-9]+)?/, m)) vr=m[0] + } + in_crtc && $0 ~ /^$/ { in_crtc=0 } + + END { + if (crtc == "" || mode == "" || active != "1") { print "-"; exit } + if (vr != "") print mode "@" vr + else print mode + }' "$state" 2>/dev/null || echo "-" +} # Echo lines: "\t\t\t\t" # Example: "card0-HDMI-A-1 connected HDMI-A 9 1920x1080" @@ -1433,8 +1502,14 @@ display_list_connectors() { name="$(basename "$d")" status="$(tr -d '\r\n' <"$d/status" 2>/dev/null)" + # enabled (best-effort; may not exist on some kernels) + enabled="unknown" + if [ -f "$d/enabled" ]; then + enabled="$(tr -d '\r\n' <"$d/enabled" 2>/dev/null)" + [ -z "$enabled" ] && enabled="unknown" + fi + # Derive connector type from name: cardX--N - # Strip "cardN-" prefix and trailing "-N" index. typ="$(printf '%s' "$name" \ | sed -n 's/^card[0-9]\+-\([A-Za-z0-9+]\+\(-[A-Za-z0-9+]\+\)*\)-[0-9]\+/\1/p')" [ -z "$typ" ] && typ="unknown" @@ -1442,22 +1517,27 @@ display_list_connectors() { # Modes modes_file="$d/modes" if [ -f "$modes_file" ]; then - # wc output can have spaces on BusyBox; trim mc="$(wc -l <"$modes_file" 2>/dev/null | tr -d '[:space:]')" [ -z "$mc" ] && mc=0 fm="$(head -n 1 "$modes_file" 2>/dev/null | tr -d '\r\n')" + [ -z "$fm" ] && fm="" else mc=0 - fm="" + fm="" fi - printf '%s\t%s\t%s\t%s\t%s\n' "$name" "$status" "$typ" "$mc" "$fm" + # Current mode (best-effort via debugfs helper) + cur="$(display_connector_cur_mode "$name")" + [ -z "$cur" ] && cur="-" + + # NOTE: 7 columns: name status enabled typ mc fm cur + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$name" "$status" "$enabled" "$typ" "$mc" "$fm" "$cur" found=1 done [ "$found" -eq 1 ] || return 1 return 0 } - # Return 0 if any connector is connected; else 1 display_any_attached() { for d in /sys/class/drm/*-*; do @@ -1539,85 +1619,19 @@ display_debug_snapshot() { ctx="$1" [ -z "$ctx" ] && ctx="display-snapshot" log_info "----- Display snapshot: $ctx -----" - - # DRM nodes (no ls; iterate) - nodes="" - for f in /dev/dri/card* /dev/dri/renderD*; do - if [ -e "$f" ]; then - if [ -z "$nodes" ]; then nodes="$f"; else nodes="$nodes $f"; fi - fi - done - if [ -n "$nodes" ]; then - log_info "DRM nodes: $nodes" - else - log_warn "No /dev/dri/* nodes found." - fi - - # Connectors - have=0 - # shellcheck disable=SC2039 - while IFS="$(printf '\t')" read -r name status typ mc fm; do - have=1 - if [ -n "$fm" ]; then - log_info "DRM: ${name} status=${status} type=${typ} modes=${mc} first=${fm}" - else - log_info "DRM: ${name} status=${status} type=${typ} modes=${mc}" - fi - done </dev/null || true) -EOF - [ "$have" -eq 1 ] || log_warn "No DRM connectors in /sys/class/drm." - - # Summary + weston outputs (if any) - sum="$(display_connected_summary 2>/dev/null || echo none)" - log_info "Connected summary: $sum" - display_weston_outputs | while IFS= read -r l; do - [ -n "$l" ] && log_info "$l" - done - - log_info "----- End display snapshot: $ctx -----" -} - -display_debug_snapshot() { - ctx="$1" - [ -z "$ctx" ] && ctx="display-snapshot" - log_info "----- Display snapshot: $ctx -----" - - # DRM nodes - nodes="" - for f in /dev/dri/card* /dev/dri/renderD*; do - [ -e "$f" ] && nodes="${nodes:+$nodes }$f" - done - if [ -n "$nodes" ]; then - log_info "DRM nodes: $nodes" - else - log_warn "No /dev/dri/* nodes found." - fi - # Sysfs connectors (expects display_list_connectors to print tab-separated fields) have=0 - while IFS="$(printf '\t')" read -r name status typ mc fm; do + while IFS="$(printf '\t')" read -r name status enabled typ mc fm cur; do [ -n "$name" ] || continue have=1 - if [ -n "$fm" ]; then - log_info "DRM: ${name} status=${status} type=${typ} modes=${mc} first=${fm}" - else - log_info "DRM: ${name} status=${status} type=${typ} modes=${mc}" - fi + [ -z "$fm" ] && fm="" + [ -z "$cur" ] && cur="-" + log_info "DRM: ${name} status=${status} enabled=${enabled} type=${typ} modes=${mc} first=${fm} cur=${cur}" done </dev/null || true) EOF [ "$have" -eq 1 ] || log_warn "No DRM connectors in /sys/class/drm." - # Connected summary (sysfs) - sum="$(display_connected_summary 2>/dev/null || echo none)" - log_info "Connected summary (sysfs): $sum" - - # Optional weston outputs (existing helper) - display_weston_outputs | while IFS= read -r l; do - [ -n "$l" ] && log_info "$l" - done - log_info "----- End display snapshot: $ctx -----" } diff --git a/Runner/utils/lib_display.sh b/Runner/utils/lib_display.sh new file mode 100755 index 00000000..b7cd5f25 --- /dev/null +++ b/Runner/utils/lib_display.sh @@ -0,0 +1,1017 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +############################################################################### +# DRM display + Weston + Wayland helpers +# (assumes log_info/log_warn/log_error and run_with_timeout from functestlib.sh) +############################################################################### + +############################################################################### +# Internal helpers +############################################################################### +debugfs_is_mounted() { + awk '$3=="debugfs" && $2=="/sys/kernel/debug" {found=1} END{exit(found?0:1)}' /proc/mounts 2>/dev/null +} + +debugfs_try_mount() { + [ -d /sys/kernel/debug ] || return 1 + debugfs_is_mounted && return 0 + + if command -v mount >/dev/null 2>&1; then + mount -t debugfs debugfs /sys/kernel/debug >/dev/null 2>&1 || true + fi + debugfs_is_mounted +} + +display__drm_idx_from_sysfs_connector() { + # input: "card0-HDMI-A-1" + sysfs_name="$1" + idx=$(printf '%s\n' "$sysfs_name" | sed -n 's/^card\([0-9][0-9]*\)-.*/\1/p') + case "$idx" in + ""|*[!0-9]*) return 1 ;; + esac + printf '%s\n' "$idx" + return 0 +} + +display__debugfs_state_for_idx() { + idx="$1" + [ -n "$idx" ] || return 1 + debugfs_try_mount >/dev/null 2>&1 || true + st="/sys/kernel/debug/dri/$idx/state" + [ -r "$st" ] || return 1 + printf '%s\n' "$st" + return 0 +} + +display__debugfs_crtc_name_for_connector() { + # Pass 1: find the CRTC name for a connector name (e.g., HDMI-A-1) + st="$1" + cname="$2" + [ -r "$st" ] || return 1 + [ -n "$cname" ] || return 1 + + awk -v cname="$cname" ' + $0 ~ /^connector\[[0-9]+\]:/ { + inblk=0 + p=index($0, ":") + if (p > 0) { + n=substr($0, p+1) + sub(/^[[:space:]]+/, "", n) + if (n == cname) inblk=1 + } + next + } + inblk && $1 ~ /^crtc=/ { + v=$1 + sub(/^crtc=/, "", v) + if (v != "(null)" && v != "") { print v; exit 0 } + exit 1 + } + inblk && $0 ~ /^[A-Za-z_]+\[[0-9]+\]:/ { exit 1 } + ' "$st" 2>/dev/null +} + +display__debugfs_mode_for_crtc_name() { + # Pass 2: find mode for a given CRTC name (e.g., crtc-0) anywhere in file + st="$1" + crtc_name="$2" + [ -r "$st" ] || return 1 + [ -n "$crtc_name" ] || return 1 + + awk -v want="$crtc_name" ' + function is_hz(x){ v=x+0.0; return (v>=20.0 && v<=240.0) } + $0 ~ /^crtc\[[0-9]+\]:/ { + inblk = (index($0, want) > 0) ? 1 : 0 + next + } + inblk && $1 == "mode:" { + res=""; hz="" + # Try to extract "1920x1080" if it appears quoted + if (match($0, /"[0-9]+x[0-9]+"/)) { + res=substr($0, RSTART+1, RLENGTH-2) + } + # Otherwise, scan tokens for 1920x1080 and Hz + for (i=1; i<=NF; i++) { + if (res=="" && $i ~ /^[0-9]+x[0-9]+$/) res=$i + if (hz=="" && $i ~ /^[0-9]+(\.[0-9]+)?$/ && is_hz($i)) hz=$i + } + if (res != "" && hz != "") { print res "@" hz; exit 0 } + exit 1 + } + ' "$st" 2>/dev/null +} + +drm_card_index_from_dev() { + dev="$1" + case "$dev" in + /dev/dri/card*) printf '%s\n' "${dev##*/card}" ;; + *) printf '%s\n' "" ;; + esac +} + +drm_debugfs_pick_file_for_dev() { + dev="$1" + idx=$(drm_card_index_from_dev "$dev") + [ -n "$idx" ] || return 1 + + f="/sys/kernel/debug/dri/$idx/summary" + [ -r "$f" ] && { printf '%s\n' "$f"; return 0; } + + f="/sys/kernel/debug/dri/$idx/state" + [ -r "$f" ] && { printf '%s\n' "$f"; return 0; } + + return 1 +} + +drm_debugfs_pick_state_for_dev() { + dev="$1" + idx=$(drm_card_index_from_dev "$dev") + [ -n "$idx" ] || return 1 + + f="/sys/kernel/debug/dri/$idx/state" + [ -r "$f" ] && { printf '%s\n' "$f"; return 0; } + + drm_debugfs_pick_file_for_dev "$dev" +} + +# Return current mode as "WxH@Hz" for an output (best-effort) by parsing debugfs state. +drm_debugfs_output_mode() { + dev="$1" + out_name="$2" + [ -n "$dev" ] || return 1 + [ -n "$out_name" ] || return 1 + + debugfs_try_mount >/dev/null 2>&1 || true + st=$(drm_debugfs_pick_state_for_dev "$dev" 2>/dev/null) || return 1 + + want="$out_name" + case "$want" in + card*-*) want=${want#card*-} ;; + esac + + awk -v want="$want" ' + BEGIN { in_conn=0; in_crtc=0; crtc_name=""; } + + $0 ~ "^connector\\[[0-9]+\\]:" { + in_conn = 0 + p=index($0, ":") + if (p > 0) { + n=substr($0, p+1) + sub(/^[[:space:]]+/, "", n) + if (n == want) in_conn = 1 + } + next + } + + in_conn && $1 ~ /^crtc=/ { + crtc_name=$1 + sub(/^crtc=/, "", crtc_name) + if (crtc_name == "(null)" || crtc_name == "") exit 1 + in_conn=0 + next + } + + $0 ~ "^crtc\\[[0-9]+\\]:" { + in_crtc = (crtc_name != "" && index($0, crtc_name)) ? 1 : 0 + next + } + + in_crtc && $1 == "mode:" { + res=$2 + gsub(/"/, "", res) + sub(/:$/, "", res) + hz=$3 + 0 + if (res ~ /^[0-9]+x[0-9]+$/ && hz > 0) { + print res "@" hz + exit 0 + } + exit 1 + } + ' "$st" 2>/dev/null +} + +############################################################################### +# Display snapshot helpers +############################################################################### + +display_connected_summary() { + ds_base="/sys/class/drm" + + if [ ! -d "$ds_base" ]; then + log_warn "display_connected_summary: $ds_base not found" + printf '%s\n' "none" + return 0 + fi + + ds_out="" + + for ds_path in "$ds_base"/card*-*; do + [ -e "$ds_path" ] || continue + ds_name=$(basename "$ds_path") + + case "$ds_name" in + renderD*|card[0-9]) continue ;; + esac + + case "$ds_name" in + *Writeback*) continue ;; + esac + + ds_status="" + if [ -r "$ds_path/status" ]; then + ds_status=$(tr -d '[:space:]' 2>/dev/null <"$ds_path/status") + fi + [ "$ds_status" = "connected" ] || continue + + ds_ctype="Other" + case "$ds_name" in + *HDMI*) ds_ctype="HDMI-A" ;; + *eDP*) ds_ctype="eDP" ;; + *DP*) ds_ctype="DP" ;; + *LVDS*) ds_ctype="LVDS" ;; + esac + + ds_first_mode="" + if [ -r "$ds_path/modes" ]; then + ds_first_mode=$(head -n 1 "$ds_path/modes" 2>/dev/null | tr -d '[:space:]') + fi + + ds_entry="$ds_name($ds_ctype" + [ -n "$ds_first_mode" ] && ds_entry="$ds_entry,$ds_first_mode" + ds_entry="$ds_entry)" + + if [ -z "$ds_out" ]; then + ds_out="$ds_entry" + else + ds_out="$ds_out, $ds_entry" + fi + done + + [ -z "$ds_out" ] && ds_out="none" + printf '%s\n' "$ds_out" + return 0 +} + +display_debug_snapshot() { + ds_tag="$1" + [ -n "$ds_tag" ] || ds_tag="snapshot" + + log_info "----- Display snapshot: $ds_tag -----" + + debugfs_try_mount >/dev/null 2>&1 || true + + if [ -d /dev/dri ]; then + ds_nodes="" + set -- /dev/dri/* + if [ -e "$1" ]; then + for ds_n in "$@"; do + ds_nodes="$ds_nodes $ds_n" + done + ds_nodes=${ds_nodes# } + fi + log_info "DRM nodes: ${ds_nodes:-}" + else + log_warn "/dev/dri not present" + fi + + ds_base="/sys/class/drm" + if [ -d "$ds_base" ]; then + for ds_path in "$ds_base"/card*-*; do + [ -e "$ds_path" ] || continue + ds_name=$(basename "$ds_path") + + case "$ds_name" in + renderD*|card[0-9]) continue ;; + esac + + ds_status="unknown" + if [ -r "$ds_path/status" ]; then + ds_status=$(tr -d '[:space:]' 2>/dev/null <"$ds_path/status") + fi + + ds_enabled="unknown" + if [ -r "$ds_path/enabled" ]; then + ds_enabled=$(tr -d '[:space:]' 2>/dev/null <"$ds_path/enabled") + fi + + ds_ctype="Other" + case "$ds_name" in + *HDMI*) ds_ctype="HDMI-A" ;; + *eDP*) ds_ctype="eDP" ;; + *DP*) ds_ctype="DP" ;; + *LVDS*) ds_ctype="LVDS" ;; + esac + + ds_nmodes=0 + ds_first_mode="" + if [ -r "$ds_path/modes" ]; then + ds_nmodes=$(wc -l <"$ds_path/modes" 2>/dev/null | tr -d '[:space:]') + ds_first_mode=$(head -n 1 "$ds_path/modes" 2>/dev/null | tr -d '[:space:]') + [ -n "$ds_first_mode" ] || ds_first_mode="" + [ -n "$ds_nmodes" ] || ds_nmodes=0 + fi + + ds_cur="$(display_connector_cur_mode "$ds_name" 2>/dev/null || true)" + [ -n "$ds_cur" ] || ds_cur="-" + + log_info "DRM: $ds_name status=$ds_status enabled=$ds_enabled type=$ds_ctype modes=$ds_nmodes first=$ds_first_mode cur=$ds_cur" + done + else + log_warn "display_debug_snapshot: $ds_base not found" + fi + + ds_summary=$(display_connected_summary) + log_info "Connected summary (sysfs): $ds_summary" + + log_info "----- End display snapshot: $ds_tag -----" + return 0 +} + +# Pick a "primary" connector: prefer external types (HDMI > DP/eDP > LVDS > others). +display_select_primary_connector() { + base="/sys/class/drm" + [ -d "$base" ] || return 1 + + best="" + for path in "$base"/card*-*; do + [ -e "$path" ] || continue + name=$(basename "$path") + case "$name" in + renderD*|card[0-9]) continue ;; + esac + case "$name" in + *Writeback*) continue ;; + esac + + status="unknown" + if [ -r "$path/status" ]; then + status=$(tr -d '[:space:]' 2>/dev/null <"$path/status") + fi + [ "$status" = "connected" ] || continue + + prio=999 + case "$name" in + *HDMI*) prio=1 ;; + *DP*) [ "$prio" -gt 2 ] && prio=2 ;; + *LVDS*) [ "$prio" -gt 3 ] && prio=3 ;; + esac + + if [ -z "$best" ]; then + best="$name:$prio" + else + best_prio=$(printf '%s\n' "$best" | cut -d: -f2) + [ -z "$best_prio" ] && best_prio=999 + if [ "$prio" -lt "$best_prio" ]; then + best="$name:$prio" + fi + fi + done + + [ -z "$best" ] && return 1 + printf '%s\n' "$best" | cut -d: -f1 + return 0 +} + +############################################################################### +# Weston weston.ini helpers +############################################################################### + +weston_pick_writable_config() { + candidates=" +/etc/xdg/weston/weston.ini +${XDG_CONFIG_HOME:-$HOME/.config}/weston.ini +$HOME/.config/weston.ini +" + + for cfg in $candidates; do + dir=$(dirname "$cfg") + if [ ! -d "$dir" ]; then + if ! mkdir -p "$dir" 2>/dev/null; then + continue + fi + fi + + if [ -f "$cfg" ]; then + if [ -w "$cfg" ]; then + printf '%s\n' "$cfg" + return 0 + fi + else + if : >"$cfg" 2>/dev/null; then + printf '%s\n' "$cfg" + return 0 + fi + fi + done + + return 1 +} + +weston_set_output_mode() { + out_name="$1" + mode="$2" + + if [ -z "$out_name" ]; then + log_error "weston_set_output_mode: missing output name" + return 1 + fi + if [ -z "$mode" ]; then + log_error "weston_set_output_mode: missing mode" + return 1 + fi + + cfg=$(weston_pick_writable_config) || { + log_warn "weston_set_output_mode: no writable weston.ini config found" + return 1 + } + + WESTON_OUTPUT_MODE_UPDATED=0 + export WESTON_OUTPUT_MODE_UPDATED + + cur_mode=$( + awk -v want="$out_name" ' + BEGIN{ inblk=0; name=""; mode=""; } + /^\[output\]/ { inblk=1; name=""; mode=""; next } + /^\[/ { inblk=0; next } + inblk && $0 ~ /^[[:space:]]*name[[:space:]]*=/ { + v=$0; sub(/^[[:space:]]*name[[:space:]]*=/,"",v); gsub(/^[[:space:]]+|[[:space:]]+$/,"",v); name=v; next + } + inblk && $0 ~ /^[[:space:]]*mode[[:space:]]*=/ { + v=$0; sub(/^[[:space:]]*mode[[:space:]]*=/,"",v); gsub(/^[[:space:]]+|[[:space:]]+$/,"",v); mode=v; + if (name == want) { print mode; exit 0 } + next + } + ' "$cfg" 2>/dev/null + ) + + if [ -n "$cur_mode" ] && [ "$cur_mode" = "$mode" ]; then + log_info "weston_set_output_mode: unchanged ($cfg name=$out_name mode=$mode); skipping" + WESTON_OUTPUT_MODE_UPDATED=0 + export WESTON_OUTPUT_MODE_UPDATED + return 0 + fi + + tmp="${cfg}.tmp.$$" + + awk -v ONAME="$out_name" -v OMODE="$mode" ' + BEGIN { + in_block = 0 + out_block = 0 + seen_block = 0 + } + + /^\[output]/ { + if (in_block && out_block && !seen_block) { + print "name=" ONAME + print "mode=" OMODE + seen_block = 1 + } + in_block = 1 + out_block = 0 + print + next + } + + /^\[/ { + if (in_block && out_block && !seen_block) { + print "name=" ONAME + print "mode=" OMODE + seen_block = 1 + } + in_block = 0 + out_block = 0 + print + next + } + + { + if (in_block) { + if ($0 ~ /^name[[:space:]]*=/) { + n = $0 + sub(/^name[[:space:]]*=/, "", n) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", n) + if (n == ONAME) { + out_block = 1 + } + } + if (out_block && ($0 ~ /^name[[:space:]]*=/ || $0 ~ /^mode[[:space:]]*=/)) { + next + } + } + print + } + + END { + if (!seen_block) { + print "" + print "[output]" + print "name=" ONAME + print "mode=" OMODE + } + }' "$cfg" >"$tmp" 2>/dev/null + + if ! mv "$tmp" "$cfg" 2>/dev/null; then + log_warn "weston_set_output_mode: failed to update $cfg" + rm -f "$tmp" 2>/dev/null || true + return 1 + fi + + WESTON_OUTPUT_MODE_UPDATED=1 + export WESTON_OUTPUT_MODE_UPDATED + + log_info "weston_set_output_mode: updated $cfg (name=$out_name mode=$mode)" + return 0 +} + +weston_restart_for_new_config() { + if command -v systemctl >/dev/null 2>&1; then + if systemctl restart weston.service >/dev/null 2>&1; then + log_info "weston_restart_for_new_config: restarted weston.service via systemd" + return 0 + fi + + if systemctl restart "weston@weston.service" >/dev/null 2>&1; then + log_info "weston_restart_for_new_config: restarted weston@weston.service via systemd" + return 0 + fi + fi + + if command -v weston_stop >/dev/null 2>&1 && command -v weston_start >/dev/null 2>&1; then + if weston_stop && weston_start; then + log_info "weston_restart_for_new_config: restarted Weston via weston_stop/weston_start" + return 0 + fi + log_warn "weston_restart_for_new_config: weston_stop/weston_start failed" + else + log_warn "weston_restart_for_new_config: weston_stop/weston_start helpers not available" + fi + + log_warn "weston_restart_for_new_config: unable to restart Weston automatically; config will take effect on next manual restart" + return 1 +} + +############################################################################### +# Wayland / Weston runtime helpers (unchanged) +############################################################################### + +wayland_debug_snapshot() { + tag="$1" + [ -n "$tag" ] || tag="wayland-debug" + + log_info "----- Wayland/Weston debug snapshot: $tag -----" + + pids=$(pgrep weston 2>/dev/null || true) + if [ -n "$pids" ]; then + log_info "weston PIDs: $pids" + for p in $pids; do + user=$(ps -o user= -p "$p" 2>/dev/null) + group=$(ps -o group= -p "$p" 2>/dev/null) + cmd=$(tr '\0' ' ' <"/proc/$p/cmdline" 2>/dev/null) + log_info "[ps] pid=$p user=$user group=$group cmd=$cmd" + done + else + log_warn "No weston process found" + fi + + log_info "Env now: XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-} WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-}" + + if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR" ]; then + log_info "XDG_RUNTIME_DIR content:" + for f in "$XDG_RUNTIME_DIR"/*; do + [ -e "$f" ] || continue + log_info "[rt] $f" + done + fi + + log_info "----- End snapshot: $tag -----" +} + +discover_wayland_socket_anywhere() { + candidates="" + if [ -n "${XDG_RUNTIME_DIR:-}" ]; then + candidates="$candidates $XDG_RUNTIME_DIR" + fi + candidates="$candidates /dev/socket/weston /run/user/0 /run/user/1000" + + for dir in $candidates; do + [ -d "$dir" ] || continue + for sock in "$dir"/wayland-*; do + [ -S "$sock" ] || continue + printf '%s\n' "$sock" + return 0 + done + done + + return 1 +} + +adopt_wayland_env_from_socket() { + sock="$1" + [ -n "$sock" ] || return 1 + + dir=$(dirname "$sock") + base=$(basename "$sock") + + export XDG_RUNTIME_DIR="$dir" + export WAYLAND_DISPLAY="$base" + + log_info "Adopted Wayland env: XDG_RUNTIME_DIR=$dir WAYLAND_DISPLAY=$base" + log_info "Reproduce with:" + log_info " export XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR'" + log_info " export WAYLAND_DISPLAY='$WAYLAND_DISPLAY'" + return 0 +} + +wayland_connection_ok() { + sock="" + if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -n "${WAYLAND_DISPLAY:-}" ]; then + sock="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + fi + [ -n "$sock" ] || sock="" + log_info "wayland_connection_ok: using socket $sock" + + if ! command -v weston-simple-egl >/dev/null 2>&1; then + log_warn "wayland_connection_ok: weston-simple-egl not available; assuming OK" + return 0 + fi + + log_info "Probing Wayland by briefly starting weston-simple-egl" + + if command -v run_with_timeout >/dev/null 2>&1; then + run_with_timeout "3s" weston-simple-egl >/dev/null 2>&1 + rc=$? + else + weston-simple-egl >/dev/null 2>&1 & + pid=$! + sleep 3 + kill "$pid" 2>/dev/null || true + rc=0 + fi + + if [ "$rc" -ne 0 ] && [ "$rc" -ne 143 ]; then + log_warn "wayland_connection_ok: weston-simple-egl probe returned $rc" + return 1 + fi + + return 0 +} + +############################################################################### +# Hz helpers +############################################################################### +hz_is_about_60() { + hz="$1" + [ -n "$hz" ] || return 1 + awk -v h="$hz" 'BEGIN{ exit (h>=58.0 && h<=62.0) ? 0 : 1 }' +} + +############################################################################### +# weston.ini gating (non-modetest path) +############################################################################### +weston_ini_primary_is_60hz() { + ini="${1:-/etc/xdg/weston/weston.ini}" + [ -r "$ini" ] || return 1 + + primary_sysfs=$(display_select_primary_connector 2>/dev/null || true) + [ -n "$primary_sysfs" ] || return 1 + weston_name=$(printf '%s\n' "$primary_sysfs" | sed 's/^card[0-9][0-9]*-//') + [ -n "$weston_name" ] || return 1 + + mode=$( + awk -v want="$weston_name" ' + BEGIN{ inblk=0; name=""; mode=""; } + /^\[output\]/ { inblk=1; name=""; mode=""; next } + /^\[/ { inblk=0; next } + inblk && $0 ~ /^[[:space:]]*name[[:space:]]*=/ { + sub(/^[[:space:]]*name[[:space:]]*=/,""); gsub(/[[:space:]]/,""); name=$0; next + } + inblk && $0 ~ /^[[:space:]]*mode[[:space:]]*=/ { + sub(/^[[:space:]]*mode[[:space:]]*=/,""); gsub(/[[:space:]]/,""); mode=$0 + if (name == want) { print mode; exit 0 } + next + } + ' "$ini" 2>/dev/null + ) + + [ -n "$mode" ] || return 1 + printf '%s\n' "$mode" | grep -Eq '@(59\.9|59\.94|60(\.|$)|60\.0|60\.00)' +} + +weston_ini_force_primary_1080p60_if_not_60() { + cfg=$(weston_pick_writable_config 2>/dev/null || true) + [ -n "$cfg" ] || cfg=/etc/xdg/weston/weston.ini + + if weston_ini_primary_is_60hz "$cfg"; then + log_info "weston_ini_force_primary_1080p60_if_not_60: weston.ini already ~60Hz for primary output; skipping" + return 0 + fi + + # Always use the debugfs/sysfs-only path now. + weston_force_primary_1080p60_if_not_60 +} + +weston_get_primary_refresh_hz() { + primary_sysfs=$(display_select_primary_connector 2>/dev/null || true) + [ -n "$primary_sysfs" ] || return 1 + + mode=$(display_connector_cur_mode "$primary_sysfs" 2>/dev/null || true) + [ -n "$mode" ] || return 1 + + hz=$(printf '%s\n' "$mode" | awk -F@ 'NF>=2{print $2; exit 0}') + [ -n "$hz" ] || return 1 + + printf '%s\n' "$hz" +} + +display_cur_size_from_state_msm() { + state="$(display_find_dri_state_file 2>/dev/null || true)" + + if [ -z "$state" ] || [ ! -r "$state" ]; then + echo "-" + return 0 + fi + + awk ' + BEGIN { good=0; crtc=0; fb=0; sz=""; } + + /^plane\[/ { good=0; crtc=0; fb=0; sz=""; next } + + /allocated by[[:space:]]*=/ { good=1; next } + + /^[[:space:]]*crtc=crtc-/ { + if ($0 !~ /\(null\)/) crtc=1 + next + } + + /^[[:space:]]*fb=/ { + s=$0 + sub(/^[[:space:]]*fb=/, "", s) + fb = s + 0 + next + } + + /^[[:space:]]*size=/ { + s=$0 + sub(/^[[:space:]]*size=/, "", s) + sub(/[[:space:]].*$/, "", s) + sz=s + if (good && crtc && fb > 0 && sz != "") { print sz; exit 0 } + next + } + + /^[[:space:]]*dst\[0\]=/ { + s=$0 + sub(/^[[:space:]]*dst\[0\]=/, "", s) + if (match(s, /^[0-9]+x[0-9]+/, m)) { + sz=m[0] + if (good && crtc && fb > 0 && sz != "") { print sz; exit 0 } + } + next + } + + END { + if (sz != "") print sz; + else print "-"; + }' "$state" 2>/dev/null +} + +display_connector_cur_mode() { + sysfs_name="$1" + [ -n "$sysfs_name" ] || { echo "-"; return 0; } + + debugfs_try_mount >/dev/null 2>&1 || true + + idx=$(printf '%s\n' "$sysfs_name" | sed -n 's/^card\([0-9][0-9]*\)-.*/\1/p') + case "$idx" in ""|*[!0-9]*) echo "-"; return 0 ;; esac + + st="/sys/kernel/debug/dri/$idx/state" + if [ ! -r "$st" ]; then + echo "-" + return 0 + fi + + prefix="card${idx}-" + cname=${sysfs_name#"$prefix"} + [ -n "$cname" ] || { echo "-"; return 0; } + + awk -v want="$cname" ' + function first_hz(line, i, v) { + for (i=1; i<=NF; i++) { + if ($i ~ /^[0-9]+(\.[0-9]+)?$/) { + v=$i+0 + if (v>=20 && v<=240) return $i + } + } + return "" + } + + BEGIN { + cur_crtc_id=""; cur_crtc_name=""; in_crtc=0; + in_conn=0; target_crtc_name=""; target_crtc_id=""; + } + + # -------- CRTC blocks (come earlier in your file) -------- + /^[[:space:]]*crtc\[[0-9]+\]:/ { + in_crtc=1 + + line=$0 + sub(/^[[:space:]]*crtc\[/, "", line) + cur_crtc_id=line + sub(/\].*$/, "", cur_crtc_id) + + cur_crtc_name=$0 + sub(/^[[:space:]]*crtc\[[0-9]+\]:[[:space:]]*/, "", cur_crtc_name) + next + } + + in_crtc && $1=="mode:" { + res=$2 + gsub(/"/, "", res) + sub(/:$/, "", res) + hz=first_hz($0) + + if (res ~ /^[0-9]+x[0-9]+$/ && hz != "") { + mode_by_id[cur_crtc_id] = res "@" hz + mode_by_name[cur_crtc_name] = res "@" hz + } + next + } + + # stop CRTC block when next top-level block starts + in_crtc && /^[[:space:]]*[A-Za-z_]+\[[0-9]+\]:/ && $0 !~ /^[[:space:]]*crtc\[/ { + in_crtc=0 + } + + # -------- Connector blocks -------- + /^[[:space:]]*connector\[[0-9]+\]:/ { + in_conn=0 + conn_name=$0 + sub(/^[[:space:]]*connector\[[0-9]+\]:[[:space:]]*/, "", conn_name) + if (conn_name == want) in_conn=1 + next + } + + in_conn { + # your format: "crtc=crtc-0" or "crtc=(null)" + if ($0 ~ /^[[:space:]]*crtc=/) { + v=$0 + sub(/^[[:space:]]*crtc=/, "", v) + if (v != "(null)" && v != "") target_crtc_name=v + in_conn=0 + next + } + next + } + + END { + if (target_crtc_name != "" && (target_crtc_name in mode_by_name)) { + print mode_by_name[target_crtc_name] + exit 0 + } + # fallback: nothing resolved + print "-" + exit 0 + } + ' "$st" 2>/dev/null +} +############################################################################### +# Unified entrypoint (non-modetest) +############################################################################### +weston_force_primary_1080p60_if_not_60() { + wf_ret=1 + + wf_primary_sysfs=$(display_select_primary_connector 2>/dev/null || true) + if [ -z "$wf_primary_sysfs" ]; then + log_warn "weston_force_primary_1080p60_if_not_60: cannot determine primary connector; skipping" + return 1 + fi + + wf_idx=$(printf '%s\n' "$wf_primary_sysfs" | sed 's/^card\([0-9][0-9]*\)-.*$/\1/') + case "$wf_idx" in ""|*[!0-9]*) wf_idx="" ;; esac + if [ -z "$wf_idx" ]; then + log_warn "weston_force_primary_1080p60_if_not_60: bad sysfs name '$wf_primary_sysfs'" + return 1 + fi + + wf_conn_name=$(printf '%s\n' "$wf_primary_sysfs" | sed 's/^card[0-9][0-9]*-//') + if [ -z "$wf_conn_name" ]; then + log_warn "weston_force_primary_1080p60_if_not_60: cannot derive connector name from '$wf_primary_sysfs'" + return 1 + fi + + wf_cur_mode="" + wf_cur_hz="" + + wf_cur_mode=$(display_connector_cur_mode "$wf_primary_sysfs" 2>/dev/null || true) + if [ -n "$wf_cur_mode" ]; then + wf_cur_hz=$(printf '%s\n' "$wf_cur_mode" | awk -F@ 'NF>=2{print $2; exit 0}') + fi + + if [ -n "$wf_cur_hz" ] && hz_is_about_60 "$wf_cur_hz"; then + log_info "weston_force_primary_1080p60_if_not_60: already ~60Hz (${wf_cur_hz}Hz); skipping" + return 0 + fi + + log_info "weston_force_primary_1080p60_if_not_60: forcing ${wf_conn_name} to 1920x1080@60 via weston.ini (cur=${wf_cur_mode:-unknown})" + + if ! command -v weston_set_output_mode >/dev/null 2>&1; then + log_warn "weston_force_primary_1080p60_if_not_60: weston_set_output_mode not found; cannot update weston.ini" + return 1 + fi + + weston_set_output_mode "$wf_conn_name" "1920x1080@60" || { + log_warn "weston_force_primary_1080p60_if_not_60: weston_set_output_mode failed" + return 1 + } + + # If Weston is not running yet, do NOT restart anything. + # Write weston.ini now; let the upcoming Weston start apply it. + wf_running=0 + if command -v weston_is_running >/dev/null 2>&1; then + if weston_is_running >/dev/null 2>&1; then + wf_running=1 + fi + else + # Prefer pgrep over grepping ps (ShellCheck SC2009) + if command -v pgrep >/dev/null 2>&1; then + if pgrep -x weston >/dev/null 2>&1; then + wf_running=1 + fi + else + # Last-resort fallback (only if pgrep is unavailable) + # shellcheck disable=SC2009 + if ps 2>/dev/null | grep -q '[w]eston'; then + wf_running=1 + fi + fi + fi + + if [ "$wf_running" -eq 1 ]; then + log_info "weston_force_primary_1080p60_if_not_60: weston is running; restarting once to apply new weston.ini..." + + if command -v weston_stop >/dev/null 2>&1; then + weston_stop >/dev/null 2>&1 || true + else + # Best-effort fallback (guarded for minimal images) - avoid SC2015 + if command -v killall >/dev/null 2>&1; then + killall weston >/dev/null 2>&1 || true + fi + + if command -v pkill >/dev/null 2>&1; then + pkill -TERM weston >/dev/null 2>&1 || true + fi + + sleep 1 + + if command -v pkill >/dev/null 2>&1; then + pkill -KILL weston >/dev/null 2>&1 || true + fi + fi + + # Restart using the same logic used by your tests (NOT systemd-only) + if command -v weston_pick_env_or_start >/dev/null 2>&1; then + weston_pick_env_or_start >/dev/null 2>&1 || true + elif command -v overlay_start_weston_drm >/dev/null 2>&1; then + overlay_start_weston_drm >/dev/null 2>&1 || true + else + log_warn "weston_force_primary_1080p60_if_not_60: no weston start helper found after stop" + fi + + if command -v wayland_connection_ok >/dev/null 2>&1; then + wayland_connection_ok >/dev/null 2>&1 || true + fi + else + log_info "weston_force_primary_1080p60_if_not_60: weston not running; weston.ini updated (will apply on next start)" + fi + + # Post-verify (bounded retries; do not stall CI) + wf_after_mode="" + wf_after_hz="" + wf_try=0 + while [ "$wf_try" -lt 5 ]; do + wf_after_mode=$(display_connector_cur_mode "$wf_primary_sysfs" 2>/dev/null || true) + if [ -n "$wf_after_mode" ]; then + wf_after_hz=$(printf '%s\n' "$wf_after_mode" | awk -F@ 'NF>=2{print $2; exit 0}') + else + wf_after_hz="" + fi + + if [ -n "$wf_after_hz" ] && hz_is_about_60 "$wf_after_hz"; then + break + fi + + wf_try=$((wf_try + 1)) + sleep 1 + done + + if [ -n "$wf_after_hz" ] && hz_is_about_60 "$wf_after_hz"; then + log_info "weston_force_primary_1080p60_if_not_60: post-verify OK (cur=${wf_after_mode})" + wf_ret=0 + else + log_warn "weston_force_primary_1080p60_if_not_60: post-verify still not ~60Hz (cur=${wf_after_mode:-unknown}); keeping best-effort" + wf_ret=0 + fi + + return "$wf_ret" +}