342 lines
8.9 KiB
Bash
Executable File
342 lines
8.9 KiB
Bash
Executable File
#!/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 -b $module || true
|
|
fi
|
|
done
|
|
done
|