From 6f7b710c51427d1bbe2031376c65fdab1a2f5551 Mon Sep 17 00:00:00 2001 From: Anssi Hannula Date: Sun, 17 Apr 2011 14:01:57 +0000 Subject: Add display_driver_helper script and add/remove nokmsboot boot option as needed. --- Makefile | 7 +- NEWS | 3 + lib/Xconfig/main.pm | 1 + lib/Xconfig/various.pm | 15 ++ tools/display_driver_helper | 461 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 tools/display_driver_helper diff --git a/Makefile b/Makefile index bdd7e0e..2a4b92b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ DESTDIR= libdir=/usr/lib bindir=/usr/bin sbindir=/usr/sbin +rootsbindir=/sbin desktopdir=/usr/share/applications autostartdir=/usr/share/autostart xinitdir=/etc/X11/xinit.d @@ -12,6 +13,7 @@ pixmapsdir=/usr/share/libDrakX/pixmaps BIN_TOOLS= SBIN_TOOLS= keyboarddrake mousedrake XFdrake +ROOTSBIN_TOOLS = display_driver_helper INLIBDEST_DIRS = lib/xf86misc all: $(INLIBDEST_DIRS) @@ -21,10 +23,10 @@ all: $(INLIBDEST_DIRS) check: @for p in `find lib -name *.pm`; do perl -cw -I$(libdir)/libDrakX $$p || exit 1; done - @for p in tools/*; do perl -cw $$p || exit 1; done + @for p in tools/*; do head -n1 $$p | grep perl || continue; perl -cw $$p || exit 1; done install: - install -d $(DESTDIR){$(libdir),$(bindir),$(sbindir),$(desktopdir),$(autostartdir),$(xinitdir),$(iconsdir),$(pixmapsdir)} + install -d $(DESTDIR){$(libdir),$(bindir),$(sbindir),$(rootsbindir),$(desktopdir),$(autostartdir),$(xinitdir),$(iconsdir),$(pixmapsdir)} install -d $(INLIBDEST_DIRS:%=$(DESTDIR)$(libdir)/libDrakX//%) cp -a lib/*.pm $(DESTDIR)$(libdir)/libDrakX/ @@ -34,6 +36,7 @@ install: (cd lib; for i in */; do install -d $(DESTDIR)$(libdir)/libDrakX/$$i ; install -m 644 $$i/*.pm $(DESTDIR)$(libdir)/libDrakX/$$i/;done) (cd tools; \ [[ -n "$(BIN_TOOLS)" ]] && install -m755 $(BIN_TOOLS) $(DESTDIR)$(bindir); \ + [[ -n "$(ROOTSBIN_TOOLS)" ]] && install -m755 $(ROOTSBIN_TOOLS) $(DESTDIR)$(rootsbindir); \ install -m755 $(SBIN_TOOLS) $(DESTDIR)$(sbindir); \ ) #install -m644 $(wildcard data/*.desktop) $(DESTDIR)$(desktopdir) diff --git a/NEWS b/NEWS index cd963b9..e5e6e56 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +- add display_driver_helper script (used by XFdrake, udev, drakx, dkms) +- add/remove nokmsboot boot option as needed + Version 0.92 - 10 April 2011 - fix runned program in Keyboard.pm (#750) diff --git a/lib/Xconfig/main.pm b/lib/Xconfig/main.pm index 7400ed9..694c4f4 100644 --- a/lib/Xconfig/main.pm +++ b/lib/Xconfig/main.pm @@ -208,6 +208,7 @@ sub write { my $only_resolution = $raw_X->is_only_resolution_modified; $raw_X->write; Xconfig::various::check_xorg_conf_symlink(); + Xconfig::various::setup_kms(); if ($X->{resolutions}[0]{bios}) { Xconfig::various::setupFB($X->{resolutions}[0]{bios}) if !$o_skip_fb_setup;; 'need_reboot'; diff --git a/lib/Xconfig/various.pm b/lib/Xconfig/various.pm index 516bd12..ab077f5 100644 --- a/lib/Xconfig/various.pm +++ b/lib/Xconfig/various.pm @@ -393,4 +393,19 @@ sub setupFB { }, $bios_vga_mode); } +sub setup_kms { + change_bootloader_config( + sub { + my ($bootloader) = @_; + my $kms_ok = run_program::rooted($::prefix, "/sbin/display_driver_helper", "--is-kms-allowed") || 0; + return if $kms_ok != bootloader::get_append_simple($bootloader, "nokmsboot"); + if ($kms_ok) { + bootloader::remove_append_simple($bootloader, "nokmsboot"); + } else { + bootloader::set_append_simple($bootloader, "nokmsboot"); + } + 1; + }); +} + 1; diff --git a/tools/display_driver_helper b/tools/display_driver_helper new file mode 100644 index 0000000..b4aa290 --- /dev/null +++ b/tools/display_driver_helper @@ -0,0 +1,461 @@ +#!/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 +# +# Licensed under terms of GPLv2 or later. +# +# When updating, check: +# - the variables below +# - check_driver function +# - check_dkms_status function +# + +echo "$(date) $*" >> /dev/tmp7 +exec 2>>/dev/tmp7 +set -x + +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 + IS_KMS=1 + # radeon needs to 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 + check_driver "$module" || 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: xorg driver +# parameter 2: 1 the check if the driver is explicitely enabled +# 0 means that check only for conflicts +check_xorg() { + local driver="$1" + local explicit_only="$2" + + eval local xorg_drivers=\"\$XORG_$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; still don't load if no xorg.conf (i.e. live cd) + [ -e "/etc/X11/xorg.conf" ] +} + +# Load the driver for the specified modalias, if configured. +# Note: no /usr +load_driver() { + local modulename + local load_default=1 + + # NOTE: UPDATE when module-init-tools is upgraded to get better performance + # modprobe has -R option: + #for modulename in $(/home/anssi/module-init-tools-3.12/build/modprobe -Rq "$1"); do + # modprobe does not have -R option: + for mod in $(/sbin/modprobe -biqvn "$1"); do + [ "$mod" = "insmod" ] && continue + modulename="${mod##*/}" + modulename="${modulename%%.*}" + + 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 "$1" +} + +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" +} + +# Check that the initrd doesn't contain disabled modules +check_initrd() { + local initrd="$1" + local initrd_drivers="$(get_initrd_kms_drivers "$initrd")" + for driver in $initrd_drivers; do + check_driver "$driver" || return 1 + done + for driver2 in $(get_active_kms_drivers); do + for driver in $initrd_drivers; do + [ "$driver" = "$driver2" ] && continue 2 + done + # An enabled module for present hardware was not in initrd + return 1 + done + return 0 +} + +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 mod in $(/sbin/modprobe --first-time -biqvn "$modalias" 2>&1); do + modulename="${mod##*/}" + modulename="${modulename%%.*}" + IS_KMS= + check_driver "$modulename" || continue + [ -n "$IS_KMS" ] && echo $modulename + done + done +} + +usage() { + cat <