Jak załadować firmware karty WiFi przed initrd/initramfs

Spis treści

Każdy kto ma laptopa wyposażonego w kartę WiFi, czy też ogólnie komputer posiadający bezprzewodową sieciówkę, ten prawdopodobnie spotkał się z błędem podobnym do tego: Direct firmware load for iwlwifi-6000g2a-6.ucode failed with error -2 . W tym przypadku sprawa dotyczyła karty Intel Corporation Centrino Advanced-N 6205 [Taylor Peak] działającej w oparciu o moduł kernela iwlwifi . W takich przypadkach zwykle wystarczy zainstalować firmware od określonego modułu i po kłopocie. No i faktycznie w Debianie jest dostępny pakiet firmware-iwlwifi , który zawiera ten potrzebny plik iwlwifi-6000g2a-6.ucode . Problem jednak w tym, że instalacja paczki z firmware niekoniecznie może nam pomóc. Ten powyższy przykład nie jest odosobniony i czasami pliki z firmware muszą być dostępne w chwili ładowania kernela do pamięci RAM czy też na etapie initramfs/initrd. W takim przypadku zainstalowanie paczki z firmware w naszym linux'ie nic nam nie da, bo pliki rezydują na niezamontowanym jeszcze dysku. Jak zatem wybrnąć z tej wydawać by się było patowej sytuacji?

Kernel, /lib/firmware/ i zaszyfrowany linux

Technicznie rzecz biorąc ilekroć instalujemy w Debianie paczki mające w nazwie firmware-* , to pliki z firmware są umieszczane w katalogu /lib/firmware/ na dysku. Te pliki są czytane przez kernel w momencie ładowania określonego modułu, np. gdy wydamy polecenie modprobe iwlwifi . Niemniej jednak, bywają sytuacje, w których ta lokalizacja nie zawsze jest dostępna. Przykładem może być zaszyfrowany system, który wymaga obecności obrazu initrd. Po tym jak kernel zostanie załadowany do pamięci RAM podczas fazy boot, to ładowany jest też obraz initrd w celu odszyfrowania dysku i zamontowania głównego systemu plików. Problem natomiast pojawia się w momencie, gdy taki moduł wymagający do poprawnego działania dodatkowych plików firmware wkompilujemy na stałe w kernel. W takim przypadku, moduł będzie obecny w kernelu od razu po załadowaniu go do pamięci operacyjnej ale pliki z firmware ciągle będą rezydować w obrębie zaszyfrowanej przestrzeni dysku. Taki stan rzeczy powoduje, że kernel nie widząc firmware zwraca poniższe błędy:

kernel: iwlwifi 0000:03:00.0: firmware: failed to load iwlwifi-6000g2a-6.ucode (-2)
kernel: iwlwifi 0000:03:00.0: firmware: failed to load iwlwifi-6000g2a-6.ucode (-2)
kernel: iwlwifi 0000:03:00.0: Direct firmware load for iwlwifi-6000g2a-6.ucode failed with error -2
kernel: iwlwifi 0000:03:00.0: firmware: failed to load iwlwifi-6000g2a-5.ucode (-2)
kernel: iwlwifi 0000:03:00.0: firmware: failed to load iwlwifi-6000g2a-5.ucode (-2)
kernel: iwlwifi 0000:03:00.0: Direct firmware load for iwlwifi-6000g2a-5.ucode failed with error -2
kernel: iwlwifi 0000:03:00.0: minimum version required: iwlwifi-6000g2a-5
kernel: iwlwifi 0000:03:00.0: maximum version supported: iwlwifi-6000g2a-6
kernel: iwlwifi 0000:03:00.0: check git://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git

Brak firmware z kolei skutkuje problemami w obsłudze sprzętu lub też całkowicie uniemożliwia mu funkcjonowanie. W tym przypadku nawet nie pojawił się interfejs sieciowy wlan0 sprawiając wrażenie, że karta sieciowa jest być może uszkodzona i nie ruszy pod linux.

EXTRA_FIRMWARE_DIR i EXTRA_FIRMWARE

Niemniej jednak, jeśli kompilujemy własny kernel dla określonej maszyny, to musimy nieco inaczej go skonfigurować, by ten zaistniały problem wyeliminować. W kernelu mamy dostępne dwie opcje CONFIG_EXTRA_FIRMWARE_DIR oraz CONFIG_EXTRA_FIRMWARE , które mogą nam pomóc uporać się z tym zadaniem. Ta pierwsza z nich przyjmuje w argumencie katalog, z którego kernel ma czytać pliki firmware podczas procesu kompilacji. Druga opcja zaś określa nazwy plików firmware (oddzielone spacjami), które chcemy załączyć. W tym przypadku te parametry trzeba ustawić w poniższy sposób:

CONFIG_EXTRA_FIRMWARE_DIR="/lib/firmware"
CONFIG_EXTRA_FIRMWARE="iwlwifi-6000g2a-6.ucode"

Taka konfiguracja sprawi, że w kernel bezpośrednio zostanie wbudowany plik firmware i nie będzie już potrzeby ładować go z zewnętrznego źródła, o czym możemy przekonać się obserwując dokładnie proces kompilacji jądra:

...
make KERNELRELEASE=5.5.7-amd64 ARCH=x86_64      KBUILD_BUILD_VERSION=18 -f ./Makefile
  DESCEND  objtool
  CALL    scripts/atomic/check-atomics.sh
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  AR      init/built-in.a
  AR      crypto/built-in.a
  GZIP    kernel/config_data.gz
  CC      kernel/configs.o
  AR      kernel/built-in.a
  CC      drivers/base/firmware_loader/fallback_table.o
  CC      drivers/base/firmware_loader/main.o
  CC      drivers/base/firmware_loader/fallback.o
  UPD     drivers/base/firmware_loader/builtin/iwlwifi-6000g2a-6.ucode.gen.S
  AS      drivers/base/firmware_loader/builtin/iwlwifi-6000g2a-6.ucode.gen.o
  AR      drivers/base/firmware_loader/builtin/built-in.a
  AR      drivers/base/firmware_loader/built-in.a
  AR      drivers/base/built-in.a
  AR      drivers/built-in.a
  GEN     .version
  CHK     include/generated/compile.h
  LD      vmlinux.o
...

Po załadowaniu się kerenla, karta WiFi powinna być już sprawna:

kernel: iwlwifi 0000:03:00.0: loaded firmware version 18.168.6.1 op_mode iwldvm
kernel: iwlwifi 0000:03:00.0: CONFIG_IWLWIFI_DEBUG disabled
kernel: iwlwifi 0000:03:00.0: CONFIG_IWLWIFI_DEBUGFS disabled
kernel: iwlwifi 0000:03:00.0: CONFIG_IWLWIFI_DEVICE_TRACING enabled
kernel: iwlwifi 0000:03:00.0: Detected Intel(R) Centrino(R) Advanced-N 6205 AGN, REV=0xB0
kernel: ieee80211 phy0: Selected rate control algorithm 'iwl-agn-rs'
Mar 05 23:02:20 morfikownia kernel: iwlwifi 0000:03:00.0: Radio type=0x1-0x2-0x0

Kopiowanie plików firmware do initrd/initramfs

Jeśli z jakichś względów to powyższe rozwiązanie nie wchodzi w grę, np. nie budujemy kernela samodzielnie, to zawsze możemy przekopiować potrzebne pliki firmware do obrazu initrd/initramfs. W tym celu potrzebny nam będzie skrypt, który trzeba umieścić w katalogu /etc/initramfs-tools/hooks/ . Stwórzmy sobie zatem plik fix_missing_firmware w tej lokalizacji (pamiętajmy o nadaniu mu praw wykonywania) oraz dodajmy do niego tę poniższą treść:

#!/bin/sh -e

# Copy missing firmware files

PREREQ=""

prereqs () { echo "${PREREQ}"; }

case "${1}" in prereqs) prereqs; exit 0 ;; esac ;

. /usr/share/initramfs-tools/hook-functions

echo -n "Copying missing firmware files... "

[ ! -d "${DESTDIR}/lib/firmware/" ] && mkdir -p ${DESTDIR}/lib/firmware/

cp /lib/firmware/iwlwifi-6000g2a-6.ucode ${DESTDIR}/lib/firmware/

echo "done."

exit 0

Zapisujemy plik i regenerujemy obraz initrd/initramfs:

# update-initramfs -u

update-initramfs: Generating /boot/initrd.img-5.5.8-amd64
Copying missing firmware files... done.

Dla pewności możemy jeszcze podejrzeć czy pliki firmware zostały uwzględnione w obrazie:

# lsinitramfs /boot/initrd.img-5.5.8-amd64 | grep firmware

usr/lib/firmware
usr/lib/firmware/iwlwifi-6000g2a-6.ucode
...

No i jak widać, są. Po restarcie maszyny, kernel powinien te pliki z obrazu initrd/initramfs podebrać automatycznie i załadować. W przypadku gdyby się tak nie stało to pozostaje nam jedynie wkompilowanie firmware bezpośrednio w kernel.

Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.