#!/bin/sh # # Display driver helper # # Copyright (c) 2010, 2011 Anssi Hannula # # - Load drivers for specified modaliases, skipping disabled display drivers # that would cause conflicts (KMS vs. vesa, KMS vs. proprietary). # - Get information about enabled driver modules # - Check that the loaded modules are correct # # Note that drivers not known by this script are handled normally, i.e. # loaded automatically as udev normally would've. # # Licensed under terms of GPLv2 or later. # # When updating, check: # - the variables below # - check_driver function # - check_dkms_status function # # DEBUG_DISPLAY_DRIVER_HELPER=yes if [ -n "$DEBUG_DISPLAY_DRIVER_HELPER" ]; then echo "$(date) $*" >> /dev/ddh_debug exec 2>>/dev/ddh_debug set -x fi export LC_ALL=C KMS_DRIVERS="i915 radeon nouveau" # module names at run-time (hence nvidia instead of nvidia*): KNOWN_MODULES="i915|radeon|nouveau|fglrx|nvidia" XORG_i915="intel" CONFLICTS_i915="" XORG_nouveau="nouveau" CONFLICTS_nouveau="nv nvidia" XORG_radeon="ati radeon" CONFLICTS_radeon="fglrx" # Note: no /usr # See end of script for descriptions of global variables. check_driver() { local name="$1" case "$name" in i915) # implicitely loaded by X.org check_xorg $name 0 || return 1 IS_KMS=1 ;; radeon) # implicitely loaded by X.org check_xorg $name 0 || return 1 # Do not load if the proprietary driver is temporarily disabled # on a PowerXpress system. # TODO: this check could be omitted if radeon was explicitely # specified in xorg.conf - but that is only known by check_xorg. check_gl /etc/fglrx/pxpress-free.ld.so.conf && return 1 IS_KMS=1 # radeon needs to be loaded before X server NEEDS_LOAD_NOW=1 ;; nouveau) # these KMS drivers require an explicit directive in xorg.conf check_xorg $name 1 || return 1 IS_KMS=1 ;; fglrx) check_xorg fglrx 1 || return 1 check_dkms fglrx || return 1 ;; nvidia) # manually installed driver or a call from check_loaded() UNSURE=1 check_xorg nvidia 1 || return 1 ;; nvidiafb) # this is only reached if nvidiafb is manually unblacklisted return 2 ;; nvidia*) [ "$name" = "nvidia_current" ] && name=nvidia-current # there are multiple co-installable driver versions, so check # the active alternative as well check_gl /etc/$name/ld.so.conf || return 1 check_xorg nvidia 1 || return 1 check_dkms $name || return 1 ;; *) # unknown, will be loaded only if no known drivers were found return 2 ;; esac return 0 } # Return success if there is no new pending DKMS build (needed to disallow # speedboot on very early boot). # Previously failed build or a missing module is counted as no pending build. # Note: no /usr check_dkms_status() { [ -e /etc/alternatives/gl_conf ] || return 0 # return fast for non-DKMS check_gl /etc/ld.so.conf.d/GL/standard.conf && return 0 local active="$(ls -l /etc/alternatives/gl_conf | awk '{ print $NF }')" local modname= case $active in /etc/nvidia*/ld.so.conf) modname="nvidia${active#*nvidia}" modname="${modname%/*}" ;; /etc/ld.so.conf.d/GL/ati.conf) modname="fglrx" ;; *) # Unknown DKMS-looking driver, # allow speedboot. return 0 ;; esac check_dkms "$modname" 1 } # Check if all loaded kernel modules have correct xorg.conf check_loaded() { for module in $(grep -oE "^($KNOWN_MODULES) " /proc/modules); do # try to unload the driver in case it is not in use before bailing check_driver "$module" || rmmod "$module" &>/dev/null || return 1 done return 0 } # Check that specified DKMS driver is not queued for build for the current # kernel. Used to check if we 1) should disable speedboot for this boot # (--check-dkms-status), and 2) if should should load the currently # existing driver (--load). Doing otherwise might cause us to load a wrong old # version of the driver that had been installed using e.g. binary DKMS # packages. # Note: no /usr check_dkms() { local driver="$1" local force="$2" # If called from DKMS itself or we are not in rc.sysinit anymore, # there are no pending builds. if [ -z "$force" ]; then [ "$DKMS_AUTOLOAD_MODULE" = "$driver" ] && return 0 [ -z "$STARTUP" ] && [ ! -f "/dev/.in_sysinit" ] && return 0 fi local found= local uname_r="$(uname -r)" for dir in /var/lib/dkms/$driver/*; do [ -e "$dir" ] || return 0 # no module, no build; or no /var [ -L "$dir" ] && continue # not a module version found=1 # module version found [ -e "$dir/$uname_r" ] && return 0 [ -e "/var/lib/dkms-binary/$driver/$(basename "$dir")/$uname_r" ] && return 0 if [ -e "$dir/build/make.log" ]; then # Build has failed for some kernel, check if it is this one. # If so, there is no point in returning 1. grep -q "^DKMS make\.log.* $uname_r " && return 0 fi done # if module versions were found but none were built for this kernel, return 1 [ -n "$found" ] && return 1 || return 0 } # Note: no /usr check_gl() { local alt_inode="$(stat -L -c%i $1 2>/dev/null)" [ -n "$alt_inode" ] || return 1 [ -n "$GL_INODE" ] || GL_INODE="$(stat -L -c%i /etc/alternatives/gl_conf 2>/dev/null)" [ "$alt_inode" = "$GL_INODE" ] || return 1 return 0 } # Note: no /usr get_xorg_drivers() { if [ -z "$XORG_DRIVERS" ]; then XORG_DRIVERS="$(cat /etc/X11/xorg.conf /etc/X11/xorg.conf.d/*.conf 2>/dev/null | awk -F'"' -vORS=' ' -vIGNORECASE=1 ' /^[[:space:]]+*section[[:space:]]+"device"/ { device=1 } /endsection/ { device=0 } /^[[:space:]]*driver[[:space:]]*".*"/ { if (device) drivers[$2]=$2 } END { for (driver in drivers) print driver } ')" [ -n "$XORG_DRIVERS" ] || XORG_DRIVERS="-" fi } # Note: no /usr # parameter 1: KMS module or xorg driver # parameter 2: 1 - check if the driver is explicitely enabled # 0 - check only for conflicts check_xorg() { local driver="$1" local explicit_only="$2" eval local xorg_drivers=\"\$XORG_$driver\" [ -n "$xorg_drivers" ] || xorg_drivers="$driver" eval local conflicts=\"\$CONFLICTS_$driver\" get_xorg_drivers conflict_found= for enabled_driver in $XORG_DRIVERS; do for xorg_driver in $xorg_drivers; do [ "$enabled_driver" = "$xorg_driver" ] && return 0 done # if the X.org driver can be loaded implicitely, check that # there are no conflicting drivers that override the driver if [ "$explicit_only" = "0" -a -z "$conflict_found" ]; then for conflict in vesa $conflicts; do if [ "$enabled_driver" = "$conflict" ]; then conflict_found=1 continue 2 # continue loop to check for an explicit load fi done fi done # in case of a conflict, do not load the module [ -n "$conflict_found" ] && return 1 # no driver is selected - don't load if explicit_only is 1 [ "$explicit_only" = "1" ] && return 1 # implicit load allowed; only load if there is evidence that this is # not a live cd or similar with automatic configuration occurring later # in the boot process (which might configure a driver conflicting with # the implicit driver, e.g. a proprietary one) # TODO: Could this be replaced with a more robust check? [ -e "/etc/X11/xorg.conf" ] || [ -e "/etc/sysconfig/harddrake2/kernels" ] || [ -e "/etc/sysconfig/harddrake2/xorg" ] || [ -e "/boot/grub/menu.lst" ] } # Load the driver for the specified modalias, if configured. # Note: no /usr load_driver() { local modalias="$1" local modulename local load_default=1 for modulename in $(/sbin/modprobe -Rq "$modalias"); do check_driver "$modulename" case $? in 1) # a driver which needs handling by this script matches # the modalias, but was not configured - do not run # the generic modprobe if no other drivers are # configured either load_default= continue ;; 2) continue ;; esac if [ -n "$IS_KMS" ]; then grep -q "^$modulename " /proc/modules && return 0 echo "$modulename" > /dev/.late_kms 2>/dev/null # If NEEDS_LOAD_NOW is not set and plymouth is running, # skip loading the driver to avoid quitting plymouth. # The driver will be loaded later by X server itself. [ -z "$NEEDS_LOAD_NOW" ] && /bin/plymouth --ping 2>/dev/null && return 0 /bin/plymouth quit 2>/dev/null fi /sbin/modprobe -b "$modulename" && return 0 done # no specially handled modules were loaded, so load all modules normally # unless $load_default was set above [ -z "$load_default" ] || /sbin/modprobe -b "$modalias" } is_kms_allowed() { for driver in $KMS_DRIVERS; do # Check all drivers for conflicts only. check_xorg $driver 0 || return 1 done return 0 } get_initrd_kms_drivers() { local initrd="$1" local kms_drivers="$(echo "$KMS_DRIVERS" | tr " " "|")" zcat "$initrd" | cpio -t --quiet | sed -nr "s,.*/($kms_drivers)\.ko.*$,\1,p" } get_active_kms_drivers() { local kms_drivers= for device in $(grep -l 0x03 /sys/bus/pci/devices/0000\:0*/class); do [ -e "$device" ] || continue device="$(dirname $device)" [ -f "$device/modalias" ] || continue modalias="$(cat "$device/modalias")" for modulename in $(/sbin/modprobe -Rq "$modalias"); do IS_KMS= check_driver "$modulename" || continue [ -n "$IS_KMS" ] && echo $modulename done done } usage() { cat <