#!/bin/sh set -e . /usr/share/debconf/confmodule MISSING='/dev/.udev/firmware-missing /run/udev/firmware-missing' DENIED=/tmp/missing-firmware-denied if [ "x$1" = "x-n" ]; then NONINTERACTIVE=1 else NONINTERACTIVE="" fi IFACES="$@" log () { logger -t check-missing-firmware "$@" } # Not all drivers register themselves if firmware is missing; in that # case determine the module via the device's modalias. get_module () { local devpath=$1 if [ -d $devpath/driver ]; then # The real path of the destination of the driver/module # symlink should be something like "/sys/module/e100" basename $(readlink -f $devpath/driver/module) || true elif [ -e $devpath/modalias ]; then modalias="$(cat $devpath/modalias)" # Take the last module returned by modprobe modprobe --show-depends "$modalias" 2>/dev/null | \ sed -n -e '$s#^.*/\([^.]*\)\.ko.*$#\1#p' fi } # Some modules only try to load firmware once brought up. So bring up and # then down any interfaces specified by ethdetect. upnics() { for iface in $IFACES; do log "taking network interface $iface up/down" ip link set "$iface" up || true ip link set "$iface" down || true done } # Checks if a given module is a nic module and has an interface that # is up and has an IP address. Such modules should not be reloaded, # to avoid taking down the network after it's been configured. nic_is_configured() { module="$1" for iface in $(ip -o link show up | cut -d : -f 2); do dir="/sys/class/net/$iface/device/driver" if [ -e "$dir" ] && [ "$(basename "$(readlink "$dir")")" = "$module" ]; then if ip address show scope global dev "$iface" | grep -q 'scope global'; then return 0 fi fi done return 1 } get_fresh_dmesg() { dmesg_file=/tmp/dmesg.txt dmesg_ts=/tmp/dmesg-ts.txt # Get current dmesg: dmesg > $dmesg_file # Truncate if needed: if [ -f $dmesg_ts ]; then # Transform [foo] into \[foo\] to make it possible to search for # "^$tspattern" (-F for fixed string doesn't play well with ^ to # anchor the pattern on the left): tspattern=$(cat $dmesg_ts | sed 's,\[,\\[,;s,\],\\],') log "looking at dmesg again, restarting from $tspattern" # Find the line number for the first match, empty if not found: ln=$(grep -n "^$tspattern" $dmesg_file |sed 's/:.*//'|head -n 1) if [ ! -z "$ln" ]; then log "timestamp found, truncating dmesg accordingly" sed -i "1,$ln d" $dmesg_file else log "timestamp not found, using whole dmesg" fi else log "looking at dmesg for the first time" fi # Save the last timestamp: grep -o '^\[ *[0-9.]\+\]' $dmesg_file | tail -n 1 > $dmesg_ts log "saving timestamp for a later use: $(cat $dmesg_ts)" # Write and clean-up: cat $dmesg_file rm $dmesg_file } check_missing () { upnics # Give modules some time to request firmware. sleep 1 modules="" files="" # The linux kernel and udev no longer let us know via # /dev/.udev/firmware-missing and /run/udev/firmware-missing # which firmware files the kernel drivers look for. Check # dmesg instead. See also bug #725714. fwlist=/tmp/check-missing-firmware-dmesg.list get_fresh_dmesg | sed -rn 's/^(\[[^]]*\] )?([^ ]+) [^ ]+: firmware: failed to load ([^ ]+) .*/\2 \3/p' > $fwlist while read module fwfile ; do log "looking for firmware file $fwfile requested by $module" if [ ! -e /lib/firmware/$fwfile ] ; then if grep -q "^$fwfile$" $DENIED 2>/dev/null; then log "listed in $DENIED" continue fi files="${files:+$files }$fwfile" modules="$module${modules:+ $modules}" fi done < $fwlist # This block looking in $MISSING should be removed when # hw-detect no longer should support installing using older # udev and kernel versions. for missing_dir in $MISSING do if [ ! -d "$missing_dir" ]; then log "$missing_dir does not exist, skipping" continue fi for file in $(find $missing_dir -type l); do # decode firmware filename as encoded by # udev firmware.agent fwfile="$(basename $file | sed -e 's#\\x2f#/#g')" # strip probably nonexistant firmware subdirectory devpath="$(readlink $file | sed 's/\/firmware\/.*//')" # the symlink is supposed to point to the device in /sys if ! echo "$devpath" | grep -q '^/sys/'; then devpath="/sys$devpath" fi module=$(get_module "$devpath") if [ -z "$module" ]; then log "failed to determine module from $devpath" continue fi rm -f "$file" if grep -q "^$fwfile$" $DENIED 2>/dev/null; then continue fi files="$fwfile${files:+ $files}" if [ "$module" = usbcore ]; then # Special case for USB bus, which puts the # real module information in a subdir of # the devpath. for dir in $(find "$devpath" -maxdepth 1 -mindepth 1 -type d); do module=$(get_module "$dir") if [ -n "$module" ]; then modules="$module${modules:+ $modules}" fi done else modules="$module${modules:+ $modules}" fi done done if [ -n "$modules" ]; then log "missing firmware files ($files) for $modules" return 0 else log "no missing firmware in loaded kernel modules" return 1 fi } # If found, copy firmware file; preserve subdirs. try_copy () { local fwfile=$1 local sdir file f target sdir=$(dirname $fwfile | sed "s/^\.$//") file=$(basename $fwfile) for f in "/media/$fwfile" "/media/firmware/$fwfile" \ ${sdir:+"/media/$file" "/media/firmware/$file"}; do if [ -e "$f" ]; then target="/lib/firmware${sdir:+/$sdir}" log "copying loose file $file from '$(dirname $f)' to '$target'" mkdir -p "$target" rm -f "$target/$file" cp -aL "$f" "$target" || true break fi done } first_try=1 first_ask=1 ask_load_firmware () { if [ "$first_try" ]; then first_try="" return 0 fi if [ "$NONINTERACTIVE" ]; then if [ ! "$first_ask" ]; then return 1 else first_ask="" return 0 fi fi db_subst hw-detect/load_firmware FILES "$files" if ! db_input high hw-detect/load_firmware; then if [ ! "$first_ask" ]; then exit 1; else first_ask="" fi fi if ! db_go; then exit 10 # back up fi db_get hw-detect/load_firmware if [ "$RET" = true ]; then return 0 else echo "$files" | tr ' ' '\n' >> $DENIED return 1 fi } list_deb_firmware () { udpkg -c "$1" \ | grep '^\./lib/firmware/' \ | sed -e 's!^\./lib/firmware/!!' \ | grep -v '^$' } check_deb_arch () { arch=$(udpkg -f "$1" | grep '^Architecture:' | sed -e 's/Architecture: *//') [ "$arch" = all ] || [ "$arch" = "$(udpkg --print-architecture)" ] } # Remove non-accepted firmware package remove_pkg() { pkgname="$1" # Remove all files listed in /var/lib/dpkg/info/$pkgname.md5sum for file in $(cut -d" " -f 2- /var/lib/dpkg/info/$pkgname.md5sum) ; do rm /$file done } install_firmware_pkg () { if echo "$1" | grep -q '\.deb$'; then # cache deb for installation into /target later mkdir -p /var/cache/firmware/ cp -aL "$1" /var/cache/firmware/ || true filename="$(basename "$1")" pkgname="$(echo $filename |cut -d_ -f1)" udpkg --unpack "/var/cache/firmware/$filename" if [ -f /var/lib/dpkg/info/$pkgname.preinst ] ; then # Run preinst script to see if the firmware # license is accepted Exit code of preinst # decide if the package should be installed or # not. if /var/lib/dpkg/info/$pkgname.preinst ; then : else remove_pkg "$pkgname" rm "/var/cache/firmware/$filename" fi fi else udpkg --unpack "$1" fi } # Try to load udebs (or debs) that contain the missing firmware. # This does not use anna because debs can have arbitrary # dependencies, which anna might try to install. check_for_firmware() { echo "$files" | sed -e 's/ /\n/g' >/tmp/grepfor for filename in $@; do if [ -f "$filename" ]; then if check_deb_arch "$filename" && list_deb_firmware "$filename" | grep -qf /tmp/grepfor; then log "installing firmware package $filename" install_firmware_pkg "$filename" || true fi fi done rm -f /tmp/grepfor } while check_missing && ask_load_firmware; do # first, check if needed firmware (u)debs are available on the # PXE initrd or the installation CD. if [ -d /firmware ]; then check_for_firmware /firmware/*.deb /firmware/*.udeb fi if [ -d /cdrom/firmware ]; then check_for_firmware /cdrom/firmware/*.deb /cdrom/firmware/*.udeb fi # second, look for loose firmware files on the media device. if mountmedia; then for file in $files; do try_copy "$file" done umount /media || true fi # last, look for firmware (u)debs on the media device if mountmedia driver; then check_for_firmware /media/*.deb /media/*.udeb /media/*.ude /media/firmware/*.deb /media/firmware/*.udeb /media/firmware/*.ude umount /media || true fi # remove and reload modules so they see the new firmware # Sort to only reload a given module once if it asks for more # than one firmware file (example iwlagn) for module in $(echo $modules | tr " " "\n" | sort -u); do if ! nic_is_configured $module; then log "removing and loading kernel module $module" modprobe -r $module || true modprobe $module || true fi done done