From 9a10261f5e66b4dc344ced1a697223a332b7c1f8 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 15 Jul 2026 15:39:35 +0530 Subject: [PATCH 1/3] pkg: add graphics package-set transition support Add Debian package mappings for the Qualcomm graphics overlay and upstream Mesa/freedreno base stack. Add package-set verification and removal helpers to: - avoid package-manager work when a set is already complete - remove installed overlay packages when restoring the base stack - validate package contents required by graphics backends - report whether a package transition requires reboot Signed-off-by: Srikanth Muppandam --- Runner/config/pkg_command_map.conf | 119 +++--- Runner/utils/lib_pkg_provider.sh | 610 ++++++++++++++++++++++++++++- 2 files changed, 657 insertions(+), 72 deletions(-) diff --git a/Runner/config/pkg_command_map.conf b/Runner/config/pkg_command_map.conf index 82b941f5..cd5f385f 100755 --- a/Runner/config/pkg_command_map.conf +++ b/Runner/config/pkg_command_map.conf @@ -1,40 +1,30 @@ # qcom-linux-testkit command-to-package map # # Format: -# os-id:command=package package2 ... -# provider:command=package package2 ... -# os-id:TESTNAME:command=package package2 ... -# provider:TESTNAME:command=package package2 ... -# -# Lookup order: -# 1. os-id:TESTNAME:command -# 2. provider:TESTNAME:command -# 3. os-id:command -# 4. provider:command -# -# The right-hand side is a required package set. -# All packages listed on the same line are installed when that exact -# dependency command is missing. -# -# Use TESTNAME-scoped mappings for large feature stacks so only that test -# installs those packages. -# -# The provider installs package names through the active package manager. -# It does not scrape raw package pool directories. -# -# For apt: -# - Internal repositories should be provided through *.sources files. -# - Public repositories can use the target's existing apt sources. -# - Local package locations should be exposed as a local apt repository -# through a *.sources file under: -# -# /opt/qcom-testkit/metadata/*.sources -# -# Apt selects the latest candidate version from repository metadata. -# Do not encode versions here unless strict pinning is required. +# os-id:command=package package2 ... +# provider:command=package package2 ... +# os-id:TESTNAME:command=package package2 ... +# provider:TESTNAME:command=package package2 ... +# os-id:package-set:=package package2 ... +# provider:package-set:=package package2 ... +# +# Lookup order for command dependencies: +# 1. os-id:TESTNAME:command +# 2. provider:TESTNAME:command +# 3. os-id:command +# 4. provider:command +# +# Lookup order for package sets: +# 1. os-id:package-set: +# 2. provider:package-set: # # Do not assume command name equals package name. +# --------------------------------------------------------------------------- +# Common apt provider mappings. +# Applies to Debian/Ubuntu apt-based rootfs. +# --------------------------------------------------------------------------- + apt:modetest=libdrm-tests apt:kmscube=kmscube apt:glmark2=glmark2 @@ -78,7 +68,7 @@ rpm:efivar=efivar # --------------------------------------------------------------------------- # Common opkg provider mappings. -# Override per distro if package names differ. +# Applies to opkg-based images. # --------------------------------------------------------------------------- opkg:modetest=libdrm-tests @@ -102,51 +92,50 @@ opkg:efivar=efivar # --------------------------------------------------------------------------- # Qualcomm sensors test-specific package set. # -# These mappings are TESTNAME-scoped, so sensor packages are installed only -# when the sensor test runs and check_dependencies() checks sns_test. -# -# Update TESTNAME/command if the actual sensor test uses a different -# TESTNAME or dependency binary. +# TESTNAME-scoped so sensor packages are installed only when the Sensors test +# checks for sns_test. # --------------------------------------------------------------------------- - + apt:Sensors:sns_test=qcom-sensors-api qcom-sensors-core qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-apps rpm:Sensors:sns_test=qcom-sensors-api qcom-sensors-core qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-apps opkg:Sensors:sns_test=qcom-sensors-api qcom-sensors-core qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-apps - -# If the sensor test requires development/header packages, use the below -# TESTNAME-specific mapping instead of the smaller runtime-only mapping above. -# -# apt:Sensors_Validation:sns_test=qcom-sensors-api qcom-sensors-api-dev qcom-sensors-core qcom-sensors-core-dev qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-core-dev qcom-sensors-test-apps -# rpm:Sensors_Validation:sns_test=qcom-sensors-api qcom-sensors-api-dev qcom-sensors-core qcom-sensors-core-dev qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-core-dev qcom-sensors-test-apps -# opkg:Sensors_Validation:sns_test=qcom-sensors-api qcom-sensors-api-dev qcom-sensors-core qcom-sensors-core-dev qcom-sensors-registry qcom-sensors-services qcom-sensors-test-core qcom-sensors-test-core-dev qcom-sensors-test-apps # --------------------------------------------------------------------------- -# Optional OS-specific overrides can be added below. +# Qualcomm FastRPC package set. # -# OS-specific mappings override provider mappings. +# Used by tests that explicitly call: +# pkg_ensure_package_set fastrpc +# or: +# pkg_ensure_optional_package_set fastrpc ... # -# Examples: -# qcom-distro:media-ctl=v4l-utils -# qcom-distro:Video_V4L2_Runner:yavta=qcom-yavta -# debian:modetest=libdrm-tests -# ubuntu:modetest=libdrm-tests -# centos:modetest=libdrm-tests +# No qcom-distro/rpm mapping is provided, so Yocto remains no-op unless a +# supported mapping is added later. # --------------------------------------------------------------------------- +debian:package-set:fastrpc=fastrpc-support fastrpc-tests +ubuntu:package-set:fastrpc=fastrpc-support fastrpc-tests + # --------------------------------------------------------------------------- -# Generic package sets. +# Qualcomm Graphics / Adreno optional overlay package set. # -# Format: -# provider:package-set:=pkg1 pkg2 ... -# os-id:package-set:=pkg1 pkg2 ... +# Installed only when the test explicitly requests optional overlay recovery, +# for example: +# ./run.sh --overlay +# +# Currently available from qli-staging. Once promoted to qli, only the source +# selection in the caller needs to change. The package-set name remains graphics. # -# These are package-level dependencies, not command dependencies. -# They are useful when a test needs a package stack even if the command is -# already present or the package is expected to be installed in the rootfs. +# DKMS packages are skipped by lib_pkg_provider.sh when matching kernel headers +# are unavailable. Remaining userspace packages continue to install. +# +# No qcom-distro/rpm mapping is provided, so Yocto remains no-op. # --------------------------------------------------------------------------- -#apt:package-set:fastrpc=fastrpc-support fastrpc-tests -debian:package-set:fastrpc=fastrpc-support fastrpc-tests -ubuntu:package-set:fastrpc=fastrpc-support fastrpc-tests -# Add CentOS only when rpm package names/repo are confirmed: -# centos:package-set:fastrpc=fastrpc-support fastrpc-tests +# Qualcomm proprietary graphics overlay package set. +debian:package-set:graphics=kgsl-dkms adreno-common libgbm-msm1 adreno-gles1 adreno-gles2 adreno-egl1 adreno-vulkan-icd adreno-opencl-icd adreno-opencl-dev + +# Upstream Debian Mesa/freedreno userspace required by the default KMSCube run. +debian:package-set:graphics-base=libegl-mesa0 libgl1-mesa-dri libgbm1 libegl1 libgles2 mesa-utils + +# TODO on ubuntu +ubuntu:package-set:graphics=kgsl-dkms adreno-common adreno-gles1 adreno-gles2 adreno-egl1 adreno-vulkan-icd adreno-opencl-icd adreno-opencl-dev diff --git a/Runner/utils/lib_pkg_provider.sh b/Runner/utils/lib_pkg_provider.sh index fe36afe6..0e6ead6d 100755 --- a/Runner/utils/lib_pkg_provider.sh +++ b/Runner/utils/lib_pkg_provider.sh @@ -31,6 +31,8 @@ PKG_DEBUG="0" PKG_APT_DEBUSINE_SOURCE="${PKG_APT_DEBUSINE_SOURCE:-none}" PKG_APT_DEBUSINE_SUITE="${PKG_APT_DEBUSINE_SUITE:-auto}" PKG_PACKAGE_SET_UPGRADE="${PKG_PACKAGE_SET_UPGRADE:-1}" +PKG_DKMS_CHECK_HEADERS="${PKG_DKMS_CHECK_HEADERS:-1}" +PKG_DKMS_MISSING_HEADERS_ACTION="${PKG_DKMS_MISSING_HEADERS_ACTION:-fail}" PKG_NETWORK_REQUIRED="1" PKG_NETWORK_RECOVER="1" @@ -185,6 +187,12 @@ pkg_apply_config_entry() { package_set_upgrade) PKG_PACKAGE_SET_UPGRADE="$cfg_value" ;; + dkms_check_headers) + PKG_DKMS_CHECK_HEADERS="$cfg_value" + ;; + dkms_missing_headers_action) + PKG_DKMS_MISSING_HEADERS_ACTION="$cfg_value" + ;; *) pkg_debug "Ignoring unknown package provider config key, $cfg_key" ;; @@ -477,6 +485,277 @@ pkg_lookup_package_set() { return 1 } +# Return success when the current test invocation explicitly requests optional +# overlay package-set recovery. +# +# Default is base mode: optional package-sets are not installed unless the user +# or LAVA job passes one of the overlay flags. +# +# Supported args: +# --overlay +# --qcom-overlay +# --enable-overlay +# --base +# --no-overlay +# +# Last matching option wins. +pkg_overlay_requested_from_args() { + overlay_requested=0 + + while [ "$#" -gt 0 ]; do + case "$1" in + --overlay|--qcom-overlay|--enable-overlay) + overlay_requested=1 + ;; + --base|--no-overlay) + overlay_requested=0 + ;; + esac + + shift + done + + [ "$overlay_requested" -eq 1 ] +} + +# Return success when optional overlay package-set recovery is applicable. +# +# Yocto/qcom-distro and other embedded images must not regress. They should keep +# their existing image-provided packages and skip optional overlay package-set +# recovery unless explicitly supported later. +pkg_optional_package_set_supported_os() { + optional_os_id="$1" + + case "$optional_os_id" in + debian|ubuntu|centos) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# Ensure an optional package-set only when overlay mode is explicitly requested. +# +# This is used by tests that support both: +# base - upstream distro/rootfs packages only +# overlay - Qualcomm package-set installed over the base distro/rootfs +# +# Args: +# $1 - package-set name, for example: graphics, audio, video, display +# $2 - apt Debusine source for overlay packages, default: qli-staging +# $3 - apt Debusine suite, default: auto +# $4... - original run.sh arguments +# +# Return: +# 0 - overlay not requested, unsupported OS skipped, no mapping, or set ready +# 1 - overlay requested but package-set recovery failed +pkg_ensure_optional_package_set() { + optional_set_name="$1" + optional_apt_source="${2:-qli-staging}" + optional_apt_suite="${3:-auto}" + + if [ "$#" -ge 3 ]; then + shift 3 + else + shift "$#" + fi + + if [ -z "$optional_set_name" ]; then + pkg_log_warn "pkg_ensure_optional_package_set called with empty set name" + return 1 + fi + + # Yocto-safe default behavior: + # no --overlay means no optional package-set work at all. + if ! pkg_overlay_requested_from_args "$@"; then + pkg_log_info "Optional package-set recovery not requested, set=$optional_set_name mode=base" + return 0 + fi + + pkg_provider_init || true + + optional_provider="$(pkg_active_provider)" + optional_os_id="$(pkg_detect_os_id)" + + if ! pkg_optional_package_set_supported_os "$optional_os_id"; then + pkg_log_info "Optional package-set recovery not applicable, os=$optional_os_id provider=$optional_provider set=$optional_set_name" + return 0 + fi + + optional_packages="$(pkg_lookup_package_set "$optional_set_name" || true)" + if [ -z "$optional_packages" ]; then + pkg_log_info "Optional package-set mapping not found, os=$optional_os_id provider=$optional_provider set=$optional_set_name" + return 0 + fi + + case "$optional_provider" in + apt) + old_optional_source="${PKG_APT_DEBUSINE_SOURCE:-none}" + old_optional_suite="${PKG_APT_DEBUSINE_SUITE:-auto}" + + case "$old_optional_source" in + ""|none|disabled) + PKG_APT_DEBUSINE_SOURCE="$optional_apt_source" + ;; + esac + + case "$old_optional_suite" in + ""|auto) + PKG_APT_DEBUSINE_SUITE="$optional_apt_suite" + ;; + esac + + if [ "${PKG_APT_DEBUSINE_SOURCE:-none}" != "$old_optional_source" ] || + [ "${PKG_APT_DEBUSINE_SUITE:-auto}" != "$old_optional_suite" ]; then + rm -f "${PKG_APT_UPDATED_MARK:-/tmp/qcom_testkit_apt_updated}" 2>/dev/null || true + fi + ;; + esac + + pkg_log_info "Optional package-set recovery requested, os=$optional_os_id provider=$optional_provider set=$optional_set_name packages=$optional_packages" + + pkg_ensure_package_set "$optional_set_name" +} + +# Check whether the running kernel has headers/build tree available for DKMS. +# +# DKMS packages require a matching kernel build directory for the active kernel +# returned by uname -r. Without this, apt/rpm can leave the package in a +# half-configured state during postinst/autoinstall. +# +# Return: +# 0 - matching kernel headers/build tree found +# 1 - missing kernel version or no usable headers/build tree found +pkg_have_dkms_kernel_headers() { + dkms_kernel="$(uname -r 2>/dev/null || true)" + + [ -n "$dkms_kernel" ] || return 1 + + if [ -e "/lib/modules/${dkms_kernel}/build/Makefile" ] || + [ -e "/usr/src/linux-headers-${dkms_kernel}/Makefile" ] || + [ -e "/usr/src/kernels/${dkms_kernel}/Makefile" ]; then + return 0 + fi + + return 1 +} + +# Detect whether a package is a DKMS package. +# +# First handles common DKMS package naming patterns, then for apt providers +# checks package metadata for a Depends/PreDepends relation on "dkms". +# +# Return: +# 0 - package appears to be DKMS-backed +# 1 - package is not detected as DKMS-backed or cannot be inspected +pkg_is_dkms_package() { + dkms_pkg="$1" + + [ -n "$dkms_pkg" ] || return 1 + + case "$dkms_pkg" in + *-dkms|dkms-*) + return 0 + ;; + esac + + if [ "$(pkg_active_provider 2>/dev/null || true)" = "apt" ] && + command -v apt-cache >/dev/null 2>&1; then + apt-cache depends "$dkms_pkg" 2>/dev/null | + grep -Eq '^[[:space:]]*(Depends|PreDepends):[[:space:]]*dkms([[:space:]]|$)' && + return 0 + fi + + return 1 +} + +# Preflight DKMS package install/upgrade operations. +# +# This prevents package recovery from attempting to install or upgrade DKMS +# packages when matching kernel headers are missing. That avoids leaving dpkg/rpm +# in a broken or half-configured state. +# +# Controlled by: +# PKG_DKMS_CHECK_HEADERS=1|0 +# PKG_DKMS_MISSING_HEADERS_ACTION=skip|fail +# +# Return: +# 0 - not a DKMS package, DKMS check disabled, or headers are available +# 1 - DKMS headers are missing and policy is "fail" +# 2 - DKMS headers are missing and policy is "skip" +pkg_dkms_preflight() { + dkms_pkg="$1" + dkms_action="$2" + + [ "${PKG_DKMS_CHECK_HEADERS:-1}" = "1" ] || return 0 + pkg_is_dkms_package "$dkms_pkg" || return 0 + pkg_have_dkms_kernel_headers && return 0 + + dkms_kernel="$(uname -r 2>/dev/null || true)" + [ -n "$dkms_kernel" ] || dkms_kernel="unknown" + + pkg_log_warn "DKMS package ${dkms_action:-operation} skipped - missing kernel headers, pkg=$dkms_pkg kernel=$dkms_kernel" + pkg_log_warn "Install linux-headers-${dkms_kernel} or provide /lib/modules/${dkms_kernel}/build before installing DKMS packages" + + case "${PKG_DKMS_MISSING_HEADERS_ACTION:-skip}" in + fail) + return 1 + ;; + skip|*) + return 2 + ;; + esac +} + +# Clean up a failed DKMS package installation so package-manager state is not +# left half-configured after a postinst/autoinstall failure. +# +# This is best-effort only. The original install failure is still returned to +# the caller. +pkg_cleanup_failed_dkms_package() { + cleanup_pkg="$1" + cleanup_provider="$(pkg_active_provider)" + + [ -n "$cleanup_pkg" ] || return 0 + pkg_is_dkms_package "$cleanup_pkg" || return 0 + + pkg_log_warn "Attempting cleanup for failed DKMS package, pkg=$cleanup_pkg provider=$cleanup_provider" + + case "$cleanup_provider" in + apt) + if command -v "$PKG_APT_GET" >/dev/null 2>&1; then + pkg_run_cmd_retry "apt-purge-failed-dkms-$cleanup_pkg" \ + env DEBIAN_FRONTEND=noninteractive \ + "$PKG_APT_GET" purge -y \ + -o "DPkg::Lock::Timeout=${PKG_APT_LOCK_TIMEOUT}" \ + "$cleanup_pkg" || true + + pkg_run_cmd_retry "apt-fix-after-failed-dkms-$cleanup_pkg" \ + env DEBIAN_FRONTEND=noninteractive \ + "$PKG_APT_GET" -f install -y \ + -o "DPkg::Lock::Timeout=${PKG_APT_LOCK_TIMEOUT}" || true + fi + ;; + rpm) + rpm_tool="$(pkg_rpm_tool || true)" + if [ -n "$rpm_tool" ]; then + pkg_run_cmd_retry "$rpm_tool-remove-failed-dkms-$cleanup_pkg" \ + "$rpm_tool" remove -y "$cleanup_pkg" || true + fi + ;; + opkg) + if command -v opkg >/dev/null 2>&1; then + pkg_run_cmd_retry "opkg-remove-failed-dkms-$cleanup_pkg" \ + opkg remove "$cleanup_pkg" || true + fi + ;; + esac + + return 0 +} + # Return installed package version for the active package provider. pkg_installed_package_version() { version_pkg="$1" @@ -1339,34 +1618,54 @@ pkg_opkg_install_package() { pkg_install_package() { install_pkg="$1" install_provider="$(pkg_active_provider)" - + if [ -z "$install_pkg" ]; then pkg_log_warn "pkg_install_package called with empty package name" return 1 fi - + if pkg_have_package "$install_pkg"; then pkg_log_package_present "$install_pkg" pkg_log_package_dependencies "$install_pkg" return 0 fi - + if ! pkg_can_install; then pkg_log_warn "Package missing and package install disabled, $install_pkg" return 1 fi - + + # DKMS packages require matching kernel headers. For mandatory overlay + # packages, missing headers should fail before apt/rpm/opkg install begins. + pkg_dkms_preflight "$install_pkg" "install" + dkms_rc=$? + case "$dkms_rc" in + 0) + ;; + 1) + return 1 + ;; + 2) + return 0 + ;; + esac + pkg_provider_summary - + + install_rc=1 + case "$install_provider" in apt) pkg_apt_install_package "$install_pkg" + install_rc=$? ;; rpm) pkg_rpm_install_package "$install_pkg" + install_rc=$? ;; opkg) pkg_opkg_install_package "$install_pkg" + install_rc=$? ;; check) pkg_log_warn "Check-only provider does not support package installation" @@ -1377,6 +1676,13 @@ pkg_install_package() { return 1 ;; esac + + if [ "$install_rc" -ne 0 ]; then + pkg_cleanup_failed_dkms_package "$install_pkg" || true + return "$install_rc" + fi + + return 0 } # Ensure a single package is installed through the active provider. @@ -1400,12 +1706,33 @@ pkg_ensure_package() { pkg_upgrade_installed_package() { upgrade_pkg="$1" upgrade_provider="$(pkg_active_provider)" - + [ -n "$upgrade_pkg" ] || return 1 - + + # DKMS package upgrades can also trigger postinst/autoinstall. Avoid that + # when matching kernel headers are missing. + pkg_dkms_preflight "$upgrade_pkg" "upgrade" + dkms_rc=$? + case "$dkms_rc" in + 0) + ;; + 1) + return 1 + ;; + 2) + return 0 + ;; + esac + case "$upgrade_provider" in apt) pkg_apt_update || return 1 + + if command -v apt-cache >/dev/null 2>&1; then + apt-cache policy "$upgrade_pkg" 2>&1 | + sed "s/^/[APT-POLICY:$upgrade_pkg] /" || true + fi + pkg_log_info "Checking package-specific upgrade, $upgrade_pkg" pkg_run_cmd_retry "apt-only-upgrade-$upgrade_pkg" \ env DEBIAN_FRONTEND=noninteractive \ @@ -1505,6 +1832,130 @@ pkg_ensure_package_set() { return 0 } +# Remove an explicit list of installed packages using the active provider. +pkg_remove_package_list() { + prpl_provider="$(pkg_active_provider)" + prpl_installed="" + + for prpl_pkg in "$@"; do + [ -n "$prpl_pkg" ] || continue + + if pkg_have_package "$prpl_pkg"; then + prpl_installed="$prpl_installed $prpl_pkg" + fi + done + + prpl_installed="$(printf '%s\n' "$prpl_installed" | sed 's/^[[:space:]]*//')" + + if [ -z "$prpl_installed" ]; then + pkg_log_pass "Requested packages are already absent" + return 0 + fi + + if ! pkg_can_install; then + return 1 + fi + + pkg_log_info "Removing package list, provider=$prpl_provider packages=$prpl_installed" + + case "$prpl_provider" in + apt) + # Intentional splitting: package names are read from the trusted map. + # shellcheck disable=SC2086 + pkg_run_cmd_retry "apt-purge-package-set" \ + env DEBIAN_FRONTEND=noninteractive \ + "$PKG_APT_GET" purge -y \ + -o "DPkg::Lock::Timeout=${PKG_APT_LOCK_TIMEOUT}" \ + $prpl_installed || return 1 + ;; + rpm) + prpl_rpm_tool="$(pkg_rpm_tool || true)" + [ -n "$prpl_rpm_tool" ] || return 1 + + # Intentional splitting: package names are read from the trusted map. + # shellcheck disable=SC2086 + pkg_run_cmd_retry "$prpl_rpm_tool-remove-package-set" \ + "$prpl_rpm_tool" remove -y $prpl_installed || return 1 + ;; + opkg) + # Intentional splitting: package names are read from the trusted map. + # shellcheck disable=SC2086 + pkg_run_cmd_retry "opkg-remove-package-set" \ + opkg remove $prpl_installed || return 1 + ;; + *) + pkg_log_fail "Provider does not support package removal, provider=$prpl_provider" + return 1 + ;; + esac + + for prpl_pkg in $prpl_installed; do + if pkg_have_package "$prpl_pkg"; then + pkg_log_fail "Package remains installed after removal, $prpl_pkg" + return 1 + fi + + pkg_log_pass "Package removed, $prpl_pkg" + done + + return 0 +} + +# Restore a named optional package set to the base image state by removing all +# currently installed packages mapped to that set. +# +# Return values: +# 0 - package set was already absent +# 1 - restore failed or package-set mapping is missing +# 2 - packages were removed successfully; reboot is required +pkg_restore_package_set() { + prps_set_name="$1" + + if [ -z "$prps_set_name" ]; then + pkg_log_warn "pkg_restore_package_set called with empty set name" + return 1 + fi + + prps_provider="$(pkg_active_provider)" + prps_os_id="$(pkg_detect_os_id)" + prps_packages="$(pkg_lookup_package_set "$prps_set_name" || true)" + + if [ -z "$prps_packages" ]; then + pkg_log_fail "No package-set mapping available for restore, set=$prps_set_name provider=$prps_provider os=$prps_os_id" + return 1 + fi + + prps_installed="" + + for prps_pkg in $prps_packages; do + [ -n "$prps_pkg" ] || continue + + if pkg_have_package "$prps_pkg"; then + prps_installed="$prps_installed $prps_pkg" + fi + done + + prps_installed="$(printf '%s\n' "$prps_installed" | sed 's/^[[:space:]]*//')" + + if [ -z "$prps_installed" ]; then + pkg_log_pass "Optional package set already restored to base state, set=$prps_set_name" + return 0 + fi + + pkg_log_info "Restoring base package state, set=$prps_set_name installed_overlay_packages=$prps_installed" + + # Intentional splitting: package names are read from the trusted map. + # shellcheck disable=SC2086 + if ! pkg_remove_package_list $prps_installed; then + pkg_log_fail "Failed to remove optional package set, set=$prps_set_name" + return 1 + fi + + pkg_log_pass "Optional package set removed, set=$prps_set_name" + pkg_log_warn "A reboot is required before the base kernel driver can own the device" + return 2 +} + # Ensure command is available, recovering missing commands through mapped packages. pkg_ensure_command() { ensure_cmd="$1" @@ -1573,3 +2024,148 @@ pkg_provider_summary() { ;; esac } + +############################################################################### +# Package-set state helpers +############################################################################### +# Return success when a mapped package set contains a package name. +pkg_package_set_contains() { + ppsc_set="$1" + ppsc_package="$2" + ppsc_packages="" + + [ -n "$ppsc_set" ] || return 1 + [ -n "$ppsc_package" ] || return 1 + + if ! command -v pkg_lookup_package_set >/dev/null 2>&1; then + return 1 + fi + + ppsc_packages="$(pkg_lookup_package_set "$ppsc_set" 2>/dev/null || true)" + + case " $ppsc_packages " in + *" $ppsc_package "*) + return 0 + ;; + esac + + return 1 +} + +# Return success only when every package in a mapped set is installed. +pkg_verify_package_set_installed() { + pvpsi_set="$1" + pvpsi_packages="" + pvpsi_missing="" + + [ -n "$pvpsi_set" ] || return 1 + + if ! command -v pkg_lookup_package_set >/dev/null 2>&1; then + log_error "Package-set lookup helper is unavailable" + return 1 + fi + + pvpsi_packages="$(pkg_lookup_package_set "$pvpsi_set" 2>/dev/null || true)" + + if [ -z "$pvpsi_packages" ]; then + log_error "Package-set mapping is unavailable: $pvpsi_set" + return 1 + fi + + for pvpsi_package in $pvpsi_packages; do + [ -n "$pvpsi_package" ] || continue + + if ! pkg_have_package "$pvpsi_package"; then + pvpsi_missing="${pvpsi_missing}${pvpsi_missing:+ }$pvpsi_package" + fi + done + + if [ -n "$pvpsi_missing" ]; then + log_warn "Package set is incomplete, set=$pvpsi_set missing=$pvpsi_missing" + return 1 + fi + + log_pass "Package set is already installed, set=$pvpsi_set" + return 0 +} + +# Avoid package-manager/network work when a required package set is complete. +pkg_ensure_required_package_set_present() { + perps_set="$1" + + [ -n "$perps_set" ] || return 1 + + if pkg_verify_package_set_installed "$perps_set"; then + return 0 + fi + + if ! command -v pkg_ensure_package_set >/dev/null 2>&1; then + log_error "Required package-set helper is unavailable" + return 1 + fi + + if ! pkg_ensure_package_set "$perps_set"; then + return 1 + fi + + pkg_verify_package_set_installed "$perps_set" +} + +# Avoid package-manager/network work when an optional package set is complete. +pkg_ensure_optional_package_set_present() { + peops_set="$1" + peops_source="$2" + peops_mode="$3" + shift 3 + + [ -n "$peops_set" ] || return 1 + [ -n "$peops_source" ] || peops_source="auto" + [ -n "$peops_mode" ] || peops_mode="auto" + + if pkg_verify_package_set_installed "$peops_set"; then + return 0 + fi + + if ! command -v pkg_ensure_optional_package_set >/dev/null 2>&1; then + log_error "Optional package-set helper is unavailable" + return 1 + fi + + if ! pkg_ensure_optional_package_set \ + "$peops_set" \ + "$peops_source" \ + "$peops_mode" \ + "$@"; then + return 1 + fi + + pkg_verify_package_set_installed "$peops_set" +} + +# Return success when an installed package owns a file matching an ERE. +pkg_package_has_file_matching() { + pphfm_package="$1" + pphfm_pattern="$2" + + [ -n "$pphfm_package" ] || return 1 + [ -n "$pphfm_pattern" ] || return 1 + + if ! pkg_have_package "$pphfm_package"; then + return 1 + fi + + if command -v dpkg-query >/dev/null 2>&1; then + dpkg-query -L "$pphfm_package" 2>/dev/null | + grep -Eq "$pphfm_pattern" + return $? + fi + + if command -v rpm >/dev/null 2>&1; then + rpm -ql "$pphfm_package" 2>/dev/null | + grep -Eq "$pphfm_pattern" + return $? + fi + + return 1 +} + From 1e44bd075da563c848090685a8f05513e1bdd3fa Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 15 Jul 2026 15:39:59 +0530 Subject: [PATCH 2/3] utils: add Qualcomm GPU and display runtime helpers Add reusable helpers to: - detect MSM/freedreno and KGSL GPU boot ownership - identify unsafe KGSL reload failures - validate the requested GPU boot mode - remove stale KGSL boot configuration - select the Mesa or Adreno GLVND EGL vendor - stop and restore display-manager DRM ownership safely Signed-off-by: Srikanth Muppandam --- Runner/utils/lib_display.sh | 240 +++++++++++++++++++++++++++ Runner/utils/lib_module_reload.sh | 260 ++++++++++++++++++++++++++++++ 2 files changed, 500 insertions(+) diff --git a/Runner/utils/lib_display.sh b/Runner/utils/lib_display.sh index dff2cfad..2e1ef940 100755 --- a/Runner/utils/lib_display.sh +++ b/Runner/utils/lib_display.sh @@ -2698,3 +2698,243 @@ weston_prepare_runtime() { return 0 } + +# Ensure Qualcomm graphics package set is available when package recovery is supported. +# +# This helper is intentionally display/graphics specific only for choosing the +# package-set name and temporary Debusine source. Generic package-manager details +# such as apt/rpm/opkg behavior, DKMS header checks, package-set upgrade, and +# source installation are handled by lib_pkg_provider.sh. +# +# Args: +# $1 - package-set name, default: graphics +# $2 - Debusine source, default: qli-staging +# $3 - Debusine suite, default: auto +# +# Return: +# 0 - package-set ready, skipped cleanly, or no mapping for this provider +# 1 - package-set recovery failed +display_ensure_graphics_package_set() { + graphics_set_name="${1:-graphics}" + graphics_source="${2:-qli-staging}" + graphics_suite="${3:-auto}" + + if [ -z "${TOOLS:-}" ] || [ ! -f "$TOOLS/lib_pkg_provider.sh" ]; then + log_warn "Package provider helper not found; continuing without graphics package recovery" + return 0 + fi + + # shellcheck disable=SC1091 + . "$TOOLS/lib_pkg_provider.sh" + + # Load provider config first. Otherwise pkg_provider.conf can overwrite + # caller-provided/default values such as apt_debusine_source. + pkg_provider_init || true + + old_graphics_source="${PKG_APT_DEBUSINE_SOURCE:-none}" + old_graphics_suite="${PKG_APT_DEBUSINE_SUITE:-auto}" + + case "$old_graphics_source" in + ""|none|disabled) + PKG_APT_DEBUSINE_SOURCE="$graphics_source" + ;; + esac + + case "$old_graphics_suite" in + "") + PKG_APT_DEBUSINE_SUITE="$graphics_suite" + ;; + esac + + # If this helper changed the apt source/suite after a previous package op, + # force apt update again so the new source becomes visible. + if [ "${PKG_APT_DEBUSINE_SOURCE:-none}" != "$old_graphics_source" ] || + [ "${PKG_APT_DEBUSINE_SUITE:-auto}" != "$old_graphics_suite" ]; then + rm -f "${PKG_APT_UPDATED_MARK:-/tmp/qcom_testkit_apt_updated}" 2>/dev/null || true + fi + + pkg_log_info "Graphics package recovery source, source=${PKG_APT_DEBUSINE_SOURCE:-none} suite=${PKG_APT_DEBUSINE_SUITE:-auto} set=${graphics_set_name}" + + pkg_ensure_package_set "$graphics_set_name" +} + +############################################################################### +# GLVND EGL vendor selection +############################################################################### + +# Select a single GLVND EGL vendor for the current process and its children. +# +# Usage: +# display_select_egl_vendor mesa +# display_select_egl_vendor adreno +# display_select_egl_vendor native +display_select_egl_vendor() { + dsev_mode="${1:-native}" + dsev_json="" + dsev_mesa_json="${DISPLAY_MESA_EGL_VENDOR_JSON:-/usr/share/glvnd/egl_vendor.d/50_mesa.json}" + dsev_adreno_json="${DISPLAY_ADRENO_EGL_VENDOR_JSON:-/usr/share/glvnd/egl_vendor.d/10_adreno.json}" + + case "$dsev_mode" in + native) + unset __EGL_VENDOR_LIBRARY_FILENAMES + log_info "Using native GLVND EGL vendor discovery" + return 0 + ;; + mesa) + dsev_json="$dsev_mesa_json" + ;; + adreno) + dsev_json="$dsev_adreno_json" + ;; + *) + log_error "Unsupported EGL vendor mode: $dsev_mode" + return 1 + ;; + esac + + if [ ! -f "$dsev_json" ] || [ ! -r "$dsev_json" ]; then + log_error "EGL vendor JSON is unavailable: $dsev_json" + return 1 + fi + + if ! grep -q '"library_path"[[:space:]]*:' "$dsev_json" 2>/dev/null; then + log_error "Invalid EGL vendor JSON, library_path missing: $dsev_json" + return 1 + fi + + __EGL_VENDOR_LIBRARY_FILENAMES="$dsev_json" + export __EGL_VENDOR_LIBRARY_FILENAMES + + log_info "Selected EGL vendor mode: $dsev_mode" + log_info "Selected EGL vendor JSON: $__EGL_VENDOR_LIBRARY_FILENAMES" + return 0 +} + +############################################################################### +# Display-manager DRM ownership helpers +############################################################################### + +# Stop an active display service and record enough state to restore it. +# +# Usage: +# display_stop_service_for_drm SERVICE DRM_DEVICE STATE_FILE +# +# The state file is created only when this helper actually stops the service. +display_stop_service_for_drm() { + dssfd_service="${1:-display-manager.service}" + dssfd_drm_device="${2:-}" + dssfd_state_file="$3" + dssfd_wait=0 + + [ -n "$dssfd_state_file" ] || return 1 + rm -f "$dssfd_state_file" + + if ! command -v systemctl >/dev/null 2>&1; then + log_info "Display-manager handling skipped because systemctl is unavailable" + return 0 + fi + + if ! systemctl is-active --quiet "$dssfd_service"; then + log_info "No active display manager requires stopping" + return 0 + fi + + log_info "Stopping display manager to release DRM master: $dssfd_service" + + if [ -n "$dssfd_drm_device" ] && + command -v fuser >/dev/null 2>&1; then + fuser -v "$dssfd_drm_device" 2>&1 | + while IFS= read -r dssfd_line; do + [ -n "$dssfd_line" ] || continue + log_info "[DRM-OWNER] $dssfd_line" + done + fi + + printf '%s\n' "$dssfd_service" >"$dssfd_state_file" + + if ! systemctl stop "$dssfd_service"; then + if systemctl is-active --quiet "$dssfd_service"; then + rm -f "$dssfd_state_file" + fi + + log_error "Failed to stop display manager: $dssfd_service" + return 1 + fi + + while [ "$dssfd_wait" -lt 10 ]; do + if ! systemctl is-active --quiet "$dssfd_service"; then + break + fi + + sleep 1 + dssfd_wait=$((dssfd_wait + 1)) + done + + if systemctl is-active --quiet "$dssfd_service"; then + rm -f "$dssfd_state_file" + log_error "Display manager is still active after stop: $dssfd_service" + return 1 + fi + + if [ -n "$dssfd_drm_device" ] && + command -v fuser >/dev/null 2>&1; then + dssfd_wait=0 + + while [ "$dssfd_wait" -lt 5 ]; do + if ! fuser "$dssfd_drm_device" >/dev/null 2>&1; then + break + fi + + sleep 1 + dssfd_wait=$((dssfd_wait + 1)) + done + + if fuser "$dssfd_drm_device" >/dev/null 2>&1; then + log_warn "DRM device still has open users after stopping display manager: $dssfd_drm_device" + + fuser -v "$dssfd_drm_device" 2>&1 | + while IFS= read -r dssfd_line; do + [ -n "$dssfd_line" ] || continue + log_info "[DRM-OWNER] $dssfd_line" + done + fi + fi + + log_pass "Display manager stopped; DRM master is available for validation" + return 0 +} + +# Restore a display service previously stopped by display_stop_service_for_drm. +display_restore_service_from_state() { + drsfs_state_file="$1" + drsfs_service="" + + [ -n "$drsfs_state_file" ] || return 0 + [ -s "$drsfs_state_file" ] || return 0 + + drsfs_service="$(sed -n '1p' "$drsfs_state_file" 2>/dev/null || true)" + [ -n "$drsfs_service" ] || return 0 + + if ! command -v systemctl >/dev/null 2>&1; then + log_warn "Cannot restore display manager because systemctl is unavailable" + return 1 + fi + + if systemctl is-active --quiet "$drsfs_service"; then + rm -f "$drsfs_state_file" + log_info "Display manager is already active: $drsfs_service" + return 0 + fi + + log_info "Restoring display manager: $drsfs_service" + + if ! systemctl start "$drsfs_service" >/dev/null 2>&1; then + log_error "Failed to restore display manager: $drsfs_service" + return 1 + fi + + rm -f "$drsfs_state_file" + log_pass "Display manager restored: $drsfs_service" + return 0 +} + diff --git a/Runner/utils/lib_module_reload.sh b/Runner/utils/lib_module_reload.sh index 923f31a7..c136e77a 100755 --- a/Runner/utils/lib_module_reload.sh +++ b/Runner/utils/lib_module_reload.sh @@ -1660,3 +1660,263 @@ mrv_resolve_profiles() { return 0 } +############################################################################### +# Qualcomm GPU boot-state helpers +############################################################################### + +# Report the GPU ownership selected during the current boot. +# +# Output: +# kgsl - msm skip_gpu is enabled and KGSL owns the GPU +# msm - upstream MSM/freedreno owns the GPU +# unknown - the state is incomplete or contradictory +mrv_qcom_gpu_boot_mode() { + mqgbm_kgsl_module="${1:-msm_kgsl}" + mqgbm_kgsl_device="${2:-/dev/kgsl-3d0}" + mqgbm_msm_module="${3:-msm}" + mqgbm_skip_gpu="" + mqgbm_kgsl_loaded=0 + mqgbm_msm_loaded=0 + + if [ -r "/sys/module/$mqgbm_msm_module/parameters/skip_gpu" ]; then + mqgbm_skip_gpu="$( + cat "/sys/module/$mqgbm_msm_module/parameters/skip_gpu" \ + 2>/dev/null || true + )" + fi + + if mrv_module_loaded "$mqgbm_kgsl_module"; then + mqgbm_kgsl_loaded=1 + fi + + if mrv_module_loaded "$mqgbm_msm_module"; then + mqgbm_msm_loaded=1 + fi + + case "$mqgbm_skip_gpu" in + Y|y|1) + if [ "$mqgbm_kgsl_loaded" -eq 1 ] && + [ -e "$mqgbm_kgsl_device" ]; then + printf '%s\n' "kgsl" + return 0 + fi + ;; + N|n|0) + if [ "$mqgbm_msm_loaded" -eq 1 ] && + [ "$mqgbm_kgsl_loaded" -eq 0 ] && + [ ! -e "$mqgbm_kgsl_device" ]; then + printf '%s\n' "msm" + return 0 + fi + ;; + esac + + printf '%s\n' "unknown" + return 1 +} + +# Return success when the current boot contains the known unsafe KGSL reload +# failure. The msm_kgsl module must not be unloaded/reloaded after this event. +mrv_qcom_gpu_unsafe_reload_detected() { + if ! command -v dmesg >/dev/null 2>&1; then + return 1 + fi + + dmesg 2>/dev/null | + grep -Eq "kobject_add_internal failed for genpd:kgsl_cx_pd|cannot create duplicate filename.*/devices/genpd:kgsl_cx_pd" +} + +# Validate the requested Qualcomm GPU boot ownership. +# +# Usage: +# mrv_qcom_gpu_validate_boot_mode msm +# mrv_qcom_gpu_validate_boot_mode kgsl +# +# Return values: +# 0 - requested boot mode is active +# 1 - state is contradictory or unavailable +# 2 - the other valid boot mode is active, so reboot is required +mrv_qcom_gpu_validate_boot_mode() { + mqgvbm_expected="$1" + mqgvbm_kgsl_module="${2:-msm_kgsl}" + mqgvbm_kgsl_device="${3:-/dev/kgsl-3d0}" + mqgvbm_msm_module="${4:-msm}" + mqgvbm_mode="unknown" + mqgvbm_skip_gpu="" + + if mrv_qcom_gpu_unsafe_reload_detected; then + log_warn "Unsafe msm_kgsl reload failure detected in this boot" + log_warn "A reboot is required before further GPU validation" + return 2 + fi + + mqgvbm_mode="$( + mrv_qcom_gpu_boot_mode \ + "$mqgvbm_kgsl_module" \ + "$mqgvbm_kgsl_device" \ + "$mqgvbm_msm_module" 2>/dev/null || true + )" + + [ -n "$mqgvbm_mode" ] || mqgvbm_mode="unknown" + + if [ -r "/sys/module/$mqgvbm_msm_module/parameters/skip_gpu" ]; then + mqgvbm_skip_gpu="$( + cat "/sys/module/$mqgvbm_msm_module/parameters/skip_gpu" \ + 2>/dev/null || true + )" + [ -n "$mqgvbm_skip_gpu" ] || mqgvbm_skip_gpu="" + fi + + log_info "Detected Qualcomm GPU boot mode: $mqgvbm_mode" + log_info "$mqgvbm_msm_module skip_gpu=$mqgvbm_skip_gpu" + + if mrv_module_loaded "$mqgvbm_kgsl_module"; then + log_info "$mqgvbm_kgsl_module loaded=yes" + else + log_info "$mqgvbm_kgsl_module loaded=no" + fi + + if [ -e "$mqgvbm_kgsl_device" ]; then + log_info "KGSL device present: $mqgvbm_kgsl_device" + else + log_info "KGSL device missing: $mqgvbm_kgsl_device" + fi + + case "$mqgvbm_expected:$mqgvbm_mode" in + msm:msm|kgsl:kgsl) + return 0 + ;; + msm:kgsl|kgsl:msm) + return 2 + ;; + esac + + return 1 +} + +# Remove stale KGSL boot policy after kgsl-dkms has been removed and rebuild +# the boot metadata that can retain msm skip_gpu=1 or msm_kgsl. +# +# Arguments: +# $1 - KGSL package name, default kgsl-dkms +# $2 - force refresh: 1 when the package set changed, otherwise 0 +# +# Return values: +# 0 - no boot artifacts changed +# 1 - cleanup failed +# 2 - boot artifacts changed/refreshed; reboot is required +mrv_qcom_gpu_cleanup_kgsl_boot_artifacts() { + mqgc_kernel="${1:-$(uname -r)}" + mqgc_changed=0 + mqgc_modules_file="${MRV_QCOM_GPU_INITRAMFS_MODULES_FILE:-/etc/initramfs-tools/modules}" + + # Do not remove boot configuration while the KGSL package is installed. + if command -v dpkg-query >/dev/null 2>&1; then + if dpkg-query -W \ + -f='${db:Status-Abbrev}' \ + kgsl-dkms 2>/dev/null | + grep -q '^ii'; then + log_info "kgsl-dkms is still installed; KGSL boot artifacts are retained" + return 1 + fi + elif command -v rpm >/dev/null 2>&1; then + if rpm -q kgsl-dkms >/dev/null 2>&1; then + log_info "kgsl-dkms is still installed; KGSL boot artifacts are retained" + return 1 + fi + fi + + # Remove only known KGSL-specific configuration files. + for mqgc_path in \ + /etc/modprobe.d/kgsl-dkms.conf \ + /usr/lib/modprobe.d/kgsl-dkms.conf \ + /lib/modprobe.d/kgsl-dkms.conf \ + /etc/modules-load.d/kgsl-dkms.conf \ + /usr/lib/modules-load.d/kgsl-dkms.conf \ + /lib/modules-load.d/kgsl-dkms.conf \ + /etc/modules-load.d/msm_kgsl.conf \ + /usr/lib/modules-load.d/msm_kgsl.conf \ + /lib/modules-load.d/msm_kgsl.conf \ + /etc/initramfs-tools/hooks/kgsl \ + /etc/initramfs-tools/hooks/kgsl-dkms \ + /etc/initramfs-tools/hooks/msm_kgsl \ + /usr/share/initramfs-tools/hooks/kgsl \ + /usr/share/initramfs-tools/hooks/kgsl-dkms \ + /usr/share/initramfs-tools/hooks/msm_kgsl + do + if [ -e "$mqgc_path" ] || [ -L "$mqgc_path" ]; then + log_info "Removing stale KGSL boot artifact: $mqgc_path" + + if ! rm -f "$mqgc_path"; then + log_error "Failed to remove KGSL boot artifact: $mqgc_path" + return 2 + fi + + mqgc_changed=1 + fi + done + + # Remove only an active msm_kgsl entry from initramfs-tools/modules. + # Preserve all unrelated module entries and comments. + if [ -f "$mqgc_modules_file" ] && + grep -Eq '^[[:space:]]*msm_kgsl([[:space:]]|$)' \ + "$mqgc_modules_file" 2>/dev/null; then + mqgc_tmp="${mqgc_modules_file}.tmp.$$" + + log_info "Removing msm_kgsl from $mqgc_modules_file" + + if ! awk '$1 != "msm_kgsl"' \ + "$mqgc_modules_file" >"$mqgc_tmp"; then + rm -f "$mqgc_tmp" + log_error "Failed to filter $mqgc_modules_file" + return 2 + fi + + if ! cat "$mqgc_tmp" >"$mqgc_modules_file"; then + rm -f "$mqgc_tmp" + log_error "Failed to update $mqgc_modules_file" + return 2 + fi + + rm -f "$mqgc_tmp" + mqgc_changed=1 + fi + + # No files were modified. Do not rebuild initramfs and do not request + # another reboot. + if [ "$mqgc_changed" -eq 0 ]; then + log_pass "No stale KGSL boot artifacts found" + return 1 + fi + + if command -v depmod >/dev/null 2>&1; then + log_info "Refreshing module dependency metadata" + + if ! depmod -a "$mqgc_kernel"; then + log_error "Failed to refresh module metadata for $mqgc_kernel" + return 2 + fi + fi + + if command -v update-initramfs >/dev/null 2>&1; then + log_info "Refreshing initramfs for kernel $mqgc_kernel" + + if ! update-initramfs -u -k "$mqgc_kernel"; then + log_error "Failed to refresh initramfs for $mqgc_kernel" + return 2 + fi + elif command -v dracut >/dev/null 2>&1; then + mqgc_initramfs="/boot/initramfs-${mqgc_kernel}.img" + log_info "Refreshing initramfs for kernel $mqgc_kernel" + + if ! dracut -f "$mqgc_initramfs" "$mqgc_kernel"; then + log_error "Failed to refresh initramfs for $mqgc_kernel" + return 2 + fi + else + log_warn "No initramfs update utility found" + fi + + log_pass "Stale KGSL boot artifacts were removed" + return 0 +} From 3253489b0772864d28bbcbf34281a91cfcf3c24e Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 15 Jul 2026 15:40:19 +0530 Subject: [PATCH 3/3] kmscube: support MSM and KGSL graphics stack validation Extend the KMSCube test to support both graphics configurations: - use upstream MSM/freedreno by default - enable Qualcomm KGSL and Adreno with --overlay - install or remove the required package sets - request reboot when GPU ownership changes - verify the Qualcomm GBM backend - select the correct EGL vendor - preserve native graphics handling on Yocto images - release and restore DRM ownership around KMSCube - retain modetest output for display diagnostics Signed-off-by: Srikanth Muppandam --- .../suites/Multimedia/Graphics/KMSCube/run.sh | 457 ++++++++++++++---- 1 file changed, 375 insertions(+), 82 deletions(-) diff --git a/Runner/suites/Multimedia/Graphics/KMSCube/run.sh b/Runner/suites/Multimedia/Graphics/KMSCube/run.sh index 8963cbef..a85c204f 100755 --- a/Runner/suites/Multimedia/Graphics/KMSCube/run.sh +++ b/Runner/suites/Multimedia/Graphics/KMSCube/run.sh @@ -7,11 +7,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env" break fi + SEARCH=$(dirname "$SEARCH") done @@ -20,115 +22,365 @@ if [ -z "$INIT_ENV" ]; then exit 1 fi -# Only source once (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then +if [ -z "${__INIT_ENV_LOADED:-}" ]; then # shellcheck disable=SC1090 . "$INIT_ENV" fi -# Always source functestlib.sh, using $TOOLS exported by init_env # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" # shellcheck disable=SC1090,SC1091 . "$TOOLS/lib_display.sh" +if [ -r "$TOOLS/lib_pkg_provider.sh" ]; then + # shellcheck disable=SC1090,SC1091 + . "$TOOLS/lib_pkg_provider.sh" +fi + +if [ -r "$TOOLS/lib_module_reload.sh" ]; then + # shellcheck disable=SC1090,SC1091 + . "$TOOLS/lib_module_reload.sh" +fi + # --- Test metadata ----------------------------------------------------------- TESTNAME="KMSCube" -FRAME_COUNT="${FRAME_COUNT:-999}" # allow override via env -EXPECTED_MIN=$((FRAME_COUNT - 1)) # tolerate off-by-one under-reporting +FRAME_COUNT="${FRAME_COUNT:-999}" +EXPECTED_MIN=$((FRAME_COUNT - 1)) -# Ensure we run from the testcase directory so .res/logs land next to run.sh test_path="$(find_test_case_by_name "$TESTNAME")" cd "$test_path" || exit 1 RES_FILE="./$TESTNAME.res" LOG_FILE="./${TESTNAME}_run.log" + KMSCUBE_DRM_CONNECTOR="" KMSCUBE_DRM_DEV="" -rm -f "$RES_FILE" "$LOG_FILE" + +OVERLAY_REQUESTED=0 +OS_ID="unknown" +DISTRO_GPU_HANDLING_SUPPORTED=0 + +PACKAGE_TRANSITION_RC=0 +BOOT_ARTIFACT_RC=0 +GPU_BOOT_VALIDATE_RC=0 + +PACKAGE_SET_CHANGED=0 +GPU_BOOT_ARTIFACTS_CHANGED=0 + +GPU_MODULE="msm_kgsl" +GPU_OVERLAY_DEVICE="/dev/kgsl-3d0" +GPU_OVERLAY_GBM_PACKAGE="${GPU_OVERLAY_GBM_PACKAGE:-libgbm-msm1}" + +DISPLAY_MANAGER_SERVICE="${DISPLAY_MANAGER_SERVICE:-display-manager.service}" +DISPLAY_MANAGER_STATE_FILE="/tmp/qcom-testkit-${TESTNAME}-display-manager.$$.state" +weston_stopped_by_test=0 + +rm -f "$RES_FILE" "$LOG_FILE" "$DISPLAY_MANAGER_STATE_FILE" + +trap ' +if [ "${weston_stopped_by_test:-0}" -eq 1 ] && + command -v weston_restore_runtime >/dev/null 2>&1; then + weston_restore_runtime 15 >/dev/null 2>&1 || true +fi +if command -v display_restore_service_from_state >/dev/null 2>&1; then + display_restore_service_from_state "$DISPLAY_MANAGER_STATE_FILE" >/dev/null 2>&1 || true +fi +rm -f "$DISPLAY_MANAGER_STATE_FILE" +' 0 +trap 'exit 129' HUP +trap 'exit 130' INT +trap 'exit 143' TERM + +# --- Resolve requested graphics runtime mode --------------------------------- +for kmscube_arg in "$@"; do + case "$kmscube_arg" in + --overlay) + OVERLAY_REQUESTED=1 + ;; + esac +done + +# --- Detect OS once ---------------------------------------------------------- +if command -v pkg_detect_os_id >/dev/null 2>&1; then + OS_ID="$(pkg_detect_os_id 2>/dev/null || true)" +elif [ -r /etc/os-release ]; then + OS_ID="$( + sed -n 's/^ID=//p' /etc/os-release | + head -n 1 | + tr -d '"' | + tr '[:upper:]' '[:lower:]' + )" +fi + +[ -n "$OS_ID" ] || OS_ID="unknown" + +case "$OS_ID" in + debian|ubuntu|centos|rhel|fedora) + DISTRO_GPU_HANDLING_SUPPORTED=1 + ;; +esac + +# --- Configure requested package and boot stack ------------------------------ +# Yocto/qcom-distro images keep their native image-selected graphics stack. +if [ "$DISTRO_GPU_HANDLING_SUPPORTED" -eq 1 ]; then + for required_helper in \ + pkg_package_set_contains \ + pkg_ensure_required_package_set_present \ + pkg_ensure_optional_package_set_present \ + pkg_restore_package_set \ + pkg_package_has_file_matching \ + mrv_qcom_gpu_boot_mode \ + mrv_qcom_gpu_cleanup_kgsl_boot_artifacts \ + mrv_qcom_gpu_validate_boot_mode \ + display_select_egl_vendor \ + display_stop_service_for_drm \ + display_restore_service_from_state; do + if ! command -v "$required_helper" >/dev/null 2>&1; then + log_fail "$TESTNAME FAIL - required helper is unavailable: $required_helper" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + done + + if [ "$OVERLAY_REQUESTED" -eq 1 ]; then + if ! pkg_package_set_contains graphics "$GPU_OVERLAY_GBM_PACKAGE"; then + log_fail "$TESTNAME FAIL - graphics package set is incomplete; missing $GPU_OVERLAY_GBM_PACKAGE" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + if ! pkg_ensure_optional_package_set_present \ + graphics \ + qli-staging \ + auto \ + "$@"; then + log_fail "$TESTNAME FAIL - failed to ensure Qualcomm graphics overlay package set" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + if ! pkg_package_has_file_matching \ + "$GPU_OVERLAY_GBM_PACKAGE" \ + '/gbm/msm_gbm[.]so$'; then + log_fail "$TESTNAME FAIL - Qualcomm GBM backend is unavailable from $GPU_OVERLAY_GBM_PACKAGE" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + mrv_qcom_gpu_validate_boot_mode \ + kgsl \ + "$GPU_MODULE" \ + "$GPU_OVERLAY_DEVICE" \ + msm + GPU_BOOT_VALIDATE_RC=$? + + case "$GPU_BOOT_VALIDATE_RC" in + 0) + ;; + 2) + log_skip "$TESTNAME SKIP - Qualcomm overlay packages are ready; reboot required to activate KGSL" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + log_skip "$TESTNAME SKIP - unable to confirm a valid KGSL boot runtime" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac + + if command -v ldconfig >/dev/null 2>&1 && ! ldconfig; then + log_fail "$TESTNAME FAIL - ldconfig failed after overlay package validation" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + if ! display_select_egl_vendor adreno; then + log_fail "$TESTNAME FAIL - failed to select Qualcomm Adreno EGL vendor" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + log_pass "Qualcomm overlay packages, GBM backend, and KGSL boot runtime are ready" + else + PACKAGE_SET_CHANGED=0 + GPU_BOOT_ARTIFACTS_CHANGED=0 + + pkg_restore_package_set graphics + PACKAGE_TRANSITION_RC=$? + + case "$PACKAGE_TRANSITION_RC" in + 0) + # Qualcomm overlay package set was already absent. + PACKAGE_SET_CHANGED=0 + ;; + 2) + # Qualcomm overlay packages were removed successfully. + PACKAGE_SET_CHANGED=1 + ;; + *) + log_fail "$TESTNAME FAIL - failed to remove Qualcomm graphics overlay package set" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + ;; + esac + + mrv_qcom_gpu_cleanup_kgsl_boot_artifacts + BOOT_ARTIFACT_RC=$? + + case "$BOOT_ARTIFACT_RC" in + 0) + # Stale KGSL boot artifacts were removed. The helper + # refreshed initramfs, so one reboot is required. + GPU_BOOT_ARTIFACTS_CHANGED=1 + ;; + 1) + # No stale KGSL boot artifacts were found. + GPU_BOOT_ARTIFACTS_CHANGED=0 + ;; + 2) + log_fail "$TESTNAME FAIL - failed to clean stale KGSL boot artifacts" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + ;; + *) + log_fail "$TESTNAME FAIL - unexpected KGSL boot-artifact cleanup result: rc=$BOOT_ARTIFACT_RC" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + ;; + esac + + if ! pkg_ensure_required_package_set_present graphics-base; then + log_fail "$TESTNAME FAIL - failed to ensure upstream Mesa graphics package set" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + + if [ "$PACKAGE_SET_CHANGED" -eq 1 ] || + [ "$GPU_BOOT_ARTIFACTS_CHANGED" -eq 1 ]; then + log_skip "$TESTNAME SKIP - Qualcomm graphics packages or KGSL boot artifacts changed; reboot required to activate upstream MSM/freedreno" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + + mrv_qcom_gpu_validate_boot_mode \ + msm \ + "$GPU_MODULE" \ + "$GPU_OVERLAY_DEVICE" \ + msm + GPU_BOOT_VALIDATE_RC=$? + + case "$GPU_BOOT_VALIDATE_RC" in + 0) + ;; + 2) + log_skip "$TESTNAME SKIP - current boot still uses KGSL; reboot required to restore upstream MSM/freedreno ownership" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + log_skip "$TESTNAME SKIP - unable to confirm upstream MSM/freedreno ownership" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac + + if ! display_select_egl_vendor mesa; then + log_skip "$TESTNAME SKIP - Mesa EGL vendor is unavailable" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + + log_pass "Upstream MSM/freedreno packages and boot runtime are ready" + fi +else + log_info "Graphics package-stack and GPU boot-mode handling skipped for os=$OS_ID" +fi log_info "-------------------------------------------------------------------" log_info "------------------- Starting $TESTNAME Testcase -------------------" -# --------------------------------------------------------------------------- -# Display snapshot -# --------------------------------------------------------------------------- +# --- Display snapshot -------------------------------------------------------- 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). 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 + + modetest -M msm -ac 2>&1 | + sed -n '1,200p' | + while IFS= read -r line; do + [ -n "$line" ] || continue + log_info "[modetest] $line" + done + log_info "----- End modetest -M msm -ac -----" else - log_warn "modetest not found in PATH skipping modetest snapshot." + 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) - if [ -n "$sysfs_summary" ] && [ "$sysfs_summary" != "none" ]; then + sysfs_summary="$(display_connected_summary)" + + if [ -n "$sysfs_summary" ] && + [ "$sysfs_summary" != "none" ]; then have_connector=1 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" + log_skip "$TESTNAME SKIP - no connected DRM display found" + echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi -# Select the DRM card that owns the connected display. -# This avoids kmscube auto-selecting the wrong card on multi-DRM-card systems. if command -v display_select_primary_connector >/dev/null 2>&1; then if KMSCUBE_DRM_CONNECTOR="$(display_select_primary_connector)"; then - if [ -z "$KMSCUBE_DRM_CONNECTOR" ]; then - log_warn "display_select_primary_connector returned empty output." - fi + [ -n "$KMSCUBE_DRM_CONNECTOR" ] || + log_warn "display_select_primary_connector returned empty output" else KMSCUBE_DRM_CONNECTOR="" - log_warn "display_select_primary_connector failed; connected display mapping may be unavailable." + log_warn "display_select_primary_connector failed; connected display mapping may be unavailable" fi else - log_warn "display_select_primary_connector helper not found; connected display mapping may be unavailable." + log_warn "display_select_primary_connector helper not found; connected display mapping may be unavailable" fi if command -v display_select_primary_drm_device >/dev/null 2>&1; then if KMSCUBE_DRM_DEV="$(display_select_primary_drm_device)"; then - if [ -z "$KMSCUBE_DRM_DEV" ]; then - log_warn "display_select_primary_drm_device returned empty output; kmscube will use default DRM device selection." - fi + [ -n "$KMSCUBE_DRM_DEV" ] || + log_warn "display_select_primary_drm_device returned empty output; kmscube will use default DRM device selection" else KMSCUBE_DRM_DEV="" - log_warn "display_select_primary_drm_device failed; kmscube will use default DRM device selection." + log_warn "display_select_primary_drm_device failed; kmscube will use default DRM device selection" fi else - log_warn "display_select_primary_drm_device helper not found; kmscube will use default DRM device selection." + log_warn "display_select_primary_drm_device helper not found; kmscube will use default DRM device selection" fi if [ -n "$KMSCUBE_DRM_DEV" ]; then log_info "Selected KMS connector: ${KMSCUBE_DRM_CONNECTOR:-}" log_info "Selected KMS DRM device: $KMSCUBE_DRM_DEV" else - log_warn "Could not map connected display to a DRM card; kmscube will use default device selection." + log_warn "Could not map connected display to a DRM card; kmscube will use default device selection" fi + # --- Basic DRM availability guard ------------------------------------------- set -- /dev/dri/card* 2>/dev/null + if [ ! -e "$1" ]; then - log_skip "$TESTNAME SKIP: no /dev/dri/card* nodes" + log_skip "$TESTNAME SKIP - no /dev/dri/card* nodes" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi # --- Dependencies ------------------------------------------------------------ -# With patched check_dependencies(): ask for return code instead of exit -if ! CHECK_DEPS_NO_EXIT=1 check_dependencies kmscube; then - log_skip "$TESTNAME SKIP: missing dependencies: kmscube" +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies kmscube modetest; then + log_skip "$TESTNAME SKIP - missing dependencies: kmscube and/or modetest" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi @@ -136,52 +388,65 @@ fi KMSCUBE_BIN="$(command -v kmscube 2>/dev/null || true)" log_info "Using kmscube: ${KMSCUBE_BIN:-}" -# --- Track whether this test stopped Weston ---------------------------------- -weston_stopped_by_test=0 - -# --- GPU acceleration gating (avoid auto/Wayland for kmscube) ---------------- -# KMSCube is a DRM/KMS test. Using "auto" can start/adopt Weston and steal DRM master. +# --- GPU acceleration gating ------------------------------------------------- if command -v display_is_cpu_renderer >/dev/null 2>&1; then if display_is_cpu_renderer gbm >/dev/null 2>&1; then if display_is_cpu_renderer gbm; then - log_skip "$TESTNAME SKIP: GPU HW acceleration not enabled (CPU/software renderer detected on GBM)" + log_skip "$TESTNAME SKIP - CPU/software renderer detected on GBM" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi else - log_warn "display_is_cpu_renderer gbm not supported, falling back to auto (may touch Wayland/Weston)." + log_warn "display_is_cpu_renderer gbm not supported, falling back to auto" + if display_is_cpu_renderer auto; then - log_skip "$TESTNAME SKIP: GPU HW acceleration not enabled (CPU/software renderer detected)" + log_skip "$TESTNAME SKIP - CPU/software renderer detected" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi fi else - log_warn "display_is_cpu_renderer helper not found, cannot enforce GPU accel gating (continuing)." + log_warn "display_is_cpu_renderer helper not found; continuing without GPU acceleration gating" fi -# --- Ensure Weston is NOT running before kmscube (DRM master) ----------------- -# Stop weston after gating too, because some helpers may have started it. +# --- Release DRM master ------------------------------------------------------ if weston_is_running; then - log_info "Weston is running, stopping it so kmscube can modeset (DRM master)" + log_info "Weston is running, stopping it so kmscube can acquire DRM master" + if weston_stop >/dev/null 2>&1; then weston_stopped_by_test=1 else log_warn "weston_stop returned non-zero, re-checking Weston state" + if ! weston_is_running; then weston_stopped_by_test=1 fi fi fi -# Double-check and be strict: kmscube will fail if weston still holds DRM master. if weston_is_running; then - log_warn "Weston still running after weston_stop, kmscube may fail to set mode" + log_warn "Weston remains running; kmscube may fail to acquire DRM master" fi -# --- Execute kmscube (avoid Wayland env leakage) ------------------------------ +case "$OS_ID" in + debian|ubuntu|centos|rhel|fedora) + if ! display_stop_service_for_drm \ + "$DISPLAY_MANAGER_SERVICE" \ + "$KMSCUBE_DRM_DEV" \ + "$DISPLAY_MANAGER_STATE_FILE"; then + log_fail "$TESTNAME FAIL - failed to release display-manager DRM ownership" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 0 + fi + ;; + *) + log_info "Display-manager handling skipped for os=$OS_ID" + ;; +esac + +# --- Execute kmscube --------------------------------------------------------- unset WAYLAND_DISPLAY -# Keep XDG_RUNTIME_DIR intact for system sanity, but force GBM platform for EGL where honored. + EGL_PLATFORM_SAVED="${EGL_PLATFORM:-}" export EGL_PLATFORM=gbm @@ -189,66 +454,94 @@ rc=0 if [ -n "$KMSCUBE_DRM_DEV" ]; then log_info "Running kmscube on $KMSCUBE_DRM_DEV with --count=${FRAME_COUNT} ..." - kmscube -D "$KMSCUBE_DRM_DEV" --count="${FRAME_COUNT}" >"$LOG_FILE" 2>&1 + + "$KMSCUBE_BIN" \ + -D "$KMSCUBE_DRM_DEV" \ + --count="${FRAME_COUNT}" >"$LOG_FILE" 2>&1 rc=$? else log_info "Running kmscube with default DRM device selection and --count=${FRAME_COUNT} ..." - kmscube --count="${FRAME_COUNT}" >"$LOG_FILE" 2>&1 + + "$KMSCUBE_BIN" \ + --count="${FRAME_COUNT}" >"$LOG_FILE" 2>&1 rc=$? fi +if [ -n "$EGL_PLATFORM_SAVED" ]; then + export EGL_PLATFORM="$EGL_PLATFORM_SAVED" +else + unset EGL_PLATFORM +fi + if [ "$rc" -ne 0 ]; then - log_fail "$TESTNAME : Execution failed (rc=$rc) — see $LOG_FILE" + log_fail "$TESTNAME : Execution failed (rc=$rc) - see $LOG_FILE" cat "$LOG_FILE" echo "$TESTNAME FAIL" >"$RES_FILE" - # Restore EGL_PLATFORM - if [ -n "$EGL_PLATFORM_SAVED" ]; then - export EGL_PLATFORM="$EGL_PLATFORM_SAVED" - else - unset EGL_PLATFORM - fi - - # Restore Weston if we stopped it if [ "$weston_stopped_by_test" -eq 1 ]; then log_info "Restoring Weston after failure" - if ! weston_restore_runtime 15; then + + if weston_restore_runtime 15; then + weston_stopped_by_test=0 + else log_error "Failed to restore Weston runtime after $TESTNAME failure" fi fi - exit 1 -fi -# Restore EGL_PLATFORM -if [ -n "$EGL_PLATFORM_SAVED" ]; then - export EGL_PLATFORM="$EGL_PLATFORM_SAVED" -else - unset EGL_PLATFORM + display_restore_service_from_state "$DISPLAY_MANAGER_STATE_FILE" || true + exit 1 fi -# --- Parse 'Rendered N frames' (case-insensitive), use the last N ------------ +# --- Parse rendered frame count ---------------------------------------------- FRAMES_RENDERED="$( - awk 'BEGIN{IGNORECASE=1} - /Rendered[[:space:]][0-9]+[[:space:]]+frames/{ - for(i=1;i<=NF;i++) if ($i ~ /^[0-9]+$/) n=$i - last=n - } - END{if (last!="") print last}' "$LOG_FILE" + awk ' + BEGIN { + IGNORECASE = 1 + } + + /Rendered[[:space:]][0-9]+[[:space:]]+frames/ { + for (i = 1; i <= NF; i++) { + if ($i ~ /^[0-9]+$/) { + n = $i + } + } + + last = n + } + + END { + if (last != "") { + print last + } + } + ' "$LOG_FILE" )" + [ -n "$FRAMES_RENDERED" ] || FRAMES_RENDERED=0 -[ "$EXPECTED_MIN" -lt 0 ] && EXPECTED_MIN=0 + +if [ "$EXPECTED_MIN" -lt 0 ]; then + EXPECTED_MIN=0 +fi + log_info "kmscube reported: Rendered ${FRAMES_RENDERED} frames (requested ${FRAME_COUNT}, min acceptable ${EXPECTED_MIN})" -# --- Restore Weston if we stopped it ----------------------------------------- restore_failed=0 + if [ "$weston_stopped_by_test" -eq 1 ]; then log_info "Restoring Weston after $TESTNAME completion" - if ! weston_restore_runtime 15; then + + if weston_restore_runtime 15; then + weston_stopped_by_test=0 + else restore_failed=1 log_error "Failed to restore Weston runtime after $TESTNAME" fi fi +if ! display_restore_service_from_state "$DISPLAY_MANAGER_STATE_FILE"; then + restore_failed=1 +fi + # --- Verdict ----------------------------------------------------------------- if [ "$FRAMES_RENDERED" -lt "$EXPECTED_MIN" ]; then log_fail "$TESTNAME : FAIL (rendered ${FRAMES_RENDERED} < ${EXPECTED_MIN})" @@ -257,7 +550,7 @@ if [ "$FRAMES_RENDERED" -lt "$EXPECTED_MIN" ]; then fi if [ "$restore_failed" -ne 0 ]; then - log_fail "$TESTNAME : FAIL (rendered ${FRAMES_RENDERED}, but Weston restore failed)" + log_fail "$TESTNAME : FAIL (rendered ${FRAMES_RENDERED}, but display runtime restore failed)" echo "$TESTNAME FAIL" >"$RES_FILE" exit 1 fi