いまさら聞けない firmware(修正版)
この記事は、一度公開しましたが、FreeBSDのカーネルモジュールを調べた際に色々と間違いや勘違いがわかり、大幅に修正する必要が生じました。そこでこの修正版を公開することにしました。FreeBSDに関する記述以外はそのままです。
最近、私が FreeBSD で RELEASE 以外のバージョン、-CURRENT や -STABLE、BETA、RC などを使うようになって、ハマることが多かったのが、"firmware が見つからない" というエラーによるカーネルパニックです。他にも、Raspberry Pi などを使っているときに、firmware という単語を頻繁に目にするようになりました。なんとなく知っているつもりだったけれど、実はよくわかっていなかった firmware について、今回はまとめてみようと思います。
FreeBSD では最大のハマりポイントになっていますが、Linux ユーザーや他の BSD ユーザーの方にも参考になるかもしれませんので、ぜひお読みください。
そもそも firmware とは?
firmware とは、プリンタやカメラを使っていると時々目にする「ファームウェアのアップデートがあります」というアレです。ハードウェア本体に内蔵されている、機器をコントロールするためのプログラムのことを指します。
PC では、CPU がインターフェイスを通じてデバイスとやり取りをします。このとき、CPU をコントロールするカーネルとデバイスの間に位置するソフトウェアが、デバイスドライバとファームウェアです。
ファームウェアといえば、Windows などで専用ツール(書き込みソフト)を使ってアップデートする場面をよく目にしたと思います。この場合、ハードウェアに内蔵された EEPROM という、ソフトウェアで書き換え可能な ROM(Read Only Memory)にファームウェアを書き込む作業です。EEPROM などの詳細については別記事にまとめましたので、興味のある方はぜひお読みください。
今回の記事で取り上げるのはこれとは異なり、最近 PC の内蔵デバイスや USB デバイスで主流となっている方式、すなわちファームウェアを揮発性メモリ(DRAM)に書き込むタイプのデバイスについてです。
揮発性メモリですから、電源を切るとメモリの内容(ファームウェア)は消えてしまいます。そのため、PC の電源を入れるたびに、カーネルがデバイスドライバを通じてファームウェアを書き込んでやる必要があります。
ファームウェアの実体は、ハードウェアの製造元が作成・配布したバイナリファイルで、多くはオープンソースではありません。冒頭で触れたトラブルは、カーネルがファームウェアを見つけられないため、カーネルパニックを起こしたというものです。
最近の FreeBSD でパニックが起きることはめったにありませんが、起きる場合の多くはこのファームウェア関係ではないかと思います。
GENERIC カーネル・kmod・firmware の関係
詳細は firmware(9) を参照のこと
FreeBSD のカーネルとドライバは、以下の 4 層に分かれています。
① GENERIC カーネルに静的にリンクされているドライバ
カーネル本体に組み込まれており、/boot/kernel/kernel として存在します。FreeBSD base system の一部で、freebsd-update でカーネル本体と一緒に更新されます。代表例は em(4)(Intel 有線 LAN)や ahci(4) などで、一般的なデバイスをカバーしています。
② base system の kmod
/boot/kernel/ 以下に .ko ファイルとして置かれ、カーネル本体と同様に freebsd-update で管理されます。起動時に動的にリンクされます。
③ ports/packages でインストールされる kmod
/boot/modules/ 以下に置かれ、pkg で管理されます。ライセンス上の理由や開発サイクルの違いから、base system とは分離されています。代表例は drm-kmod(GPU)です。drm-kmod をインストールすると、依存関係によって amdgpu-firmware-kmod や i915-firmware-kmod などの firmware パッケージも自動的に導入されます。
④ firmware バイナリ本体
/boot/modules/(bin.ko 形式)または /boot/firmware/(.bin 形式)に置かれます。base system には firmware は含まれません。両形式の詳細については後述します。
⚠ "bin.ko 形式" や ".bin 形式" というのは正式な用語ではなく、私がこの記事を書くにあたり、便宜で気につけた名称です。
この記事でしか使われませんのでご注意ください。
firmware の形式:.bin 形式と bin.ko 形式
bin.ko 形式
firmware バイナリを kmod に埋め込み、ロード時に firmware_register() でカーネルに登録する仕組みです。ファイル名は必ず _bin.ko で終わり、FreeBSD が命名するため統一されています。/boot/modules/ の中に大量の *_bin.ko ファイルが入っているのはこのためで、純粋なカーネルモジュールではなく firmware を kmod の皮で包んだものです。
loader.conf に設定して loader(8) にプリロードさせることも、起動後に kldload(8) で手動ロードすることも、devmatch や rc.d/kld による自動ロードも、通常の kmod と同じように扱えます。
.bin 形式(FreeBSD 15.0 で追加)
製造元が配布する firmware バイナリをそのまま /boot/firmware/ 以下に置く、素直な仕組みです。kmod に包む手間が不要になりました。
ここに置かれるファイルは多くが .bin で終わるファイル名になっていますが、これはデバイスの製造元が付ける名前であるため統一されたものではありません。.bin 以外の拡張子を持つファイルも混在しています。
ロードの経路は2つあります。ひとつは loader.conf に明示的に記述してプリロードする方法です。
wififw_load="YES"
wififw_name="/boot/firmware/wifi2034_fw.bin"
wififw_type="firmware"
もうひとつは firmware_get() による自動ロードです。ドライバが firmware_get() を呼んだ際、bin.ko 形式が見つからない場合に /boot/firmware/ 以下を検索し、該当ファイルがあればドライバが必要とした時点で自動的にロードします。
移行状況
15.0 以降も bin.ko 形式のまま残っている ports がありますが、.bin 形式への移行は段階的に進んでいます。bin.ko 形式と .bin 形式は現在も両方サポートされており、並列して使われています。
15.0 で .bin 形式が追加され、移行が進んでいることを考えると、将来的には Linux の linux-firmware のようにバイナリファイルを直接配置する方式に統一されていく方向ではないかと思われます。
fwget(8)
fwget は FreeBSD 14.0 で導入された、実行中のシステムで検出されたデバイスに必要な firmware パッケージを自動的にインストールするユーティリティです。PCI および USB デバイスをスキャンし、対応する firmware パッケージを pkg 経由でインストールします。
どういう時に使うか
FreeBSD をインストールした直後や、新しいハードウェアを追加したときに実行します。自分のマシンにどの firmware が必要かを把握していなくても、fwget が検出して必要なものだけをインストールしてくれます。
使用例
まず -n オプションで Dry Run し、何がインストールされるか確認します。
# fwget -n
問題なければ実際にインストールします。
# fwget
PCI デバイスだけを対象にする場合は subsystem を指定します。
# fwget pci
USB デバイスだけの場合は以下の通りです。
# fwget usb
-v オプションを付けると詳細な出力が得られます。
# fwget -v
-CURRENT / -STABLE での firmware の問題
-CURRENT や -STABLE、BETA、RC といった開発版では、pkg リポジトリのビルドがカーネルの更新に追いついていないことがあります。その結果、kmod 自体はインストールされていても、対応する firmware パッケージがリポジトリにまだ存在しない、あるいは古いバージョンのままという状態が起きます。
この状態で起動すると、kmod が必要な firmware を .bin 形式または bin.ko 形式で見つけられず、エラーになったり、最悪の場合カーネルパニックに至ります。
対処
最も安全で現実的な対処は bectl(8) で以前の BE(Boot Environment)に戻すことです。開発版を追いかける場合は、アップグレード前に必ず名前付き BE を作成しておくことが、被害を最小化するための重要な習慣です。
BE で戻せない場合は、シングルユーザーモードで起動し、/boot/loader.conf や /etc/rc.conf を編集して panic の原因となる kmod をロードしないようにして一旦起動させます。firmware がない場合も、それを探しに行く kmod を無効にしなければなりません。
一旦起動できたら、ports から make install するのが確実な方法です。pkg update && pkg upgrade で対応した firmware パッケージが届いていれば、それでも構いません。まだリポジトリが古いままの場合は、ports で make install するか、対応パッケージが届くまで待ちます。
Linux の firmware 管理:巨大アーカイブ方式
Linux のファームウェアは `linux-firmware` というパッケージ(リポジトリ)に集約されています。非常に多くのベンダーのファームウェアバイナリをまとめたもので、カーネルのソースツリーよりもサイズが大きく、現在は数 GB 規模になっています。
この設計思想は「とにかく動くようにする」というプラグマティズムで、デスクトップユーザーにとっての利便性を最優先しています。
利点としては、ユーザーが何も意識しなくても大半のデバイスが動作します。ドライバとファームウェアは同じディストリビューションのリリースサイクルで管理されるため、通常は対応関係が保たれます。
欠点としては、自分のマシンに存在しないデバイスのファームウェアが大量に含まれており、無駄が大きいことです。組み込みや軽量環境では特に問題になります。またファームウェアはバイナリブロブのため、GPL ライセンスとの兼ね合いで扱いが複雑になっており、Debian などでは `non-free-firmware` として main から分離されています。
何か問題が起きても、Linux ではファームウェアとドライバの対応関係がカーネルバージョンに縛られているため、ユーザーが個別に対処する手段が FreeBSD より少なく、必要なファイルがたった一つだったとしても、基本的にはディストリビューションのカーネル更新を待つことになります。
Windows のブルースクリーンと firmware 不一致
Windows アップデート直後に発生するブルースクリーンの一部は、このファームウェアの不一致が原因ではないかと考えられています。
Windows はドライバを Windows Update 経由で配布する仕組みを持っており、カーネル更新とドライバ更新のタイミングがずれることがあります。特に問題になるのは、OEM(メーカー)が独自にカスタマイズしたドライバを提供している場合で、Microsoft の汎用ドライバと OEM ドライバが競合したり、ファームウェアとの対応バージョンが崩れたりします。
GPU ドライバは典型例で、Windows Update がドライバを自動更新した直後に、GPU のファームウェア(VBIOS)や他のコンポーネントとの不整合でブルースクリーンが起きるケースが報告されています。
さらに Windows の場合、ドライバの品質管理という別の要因もあります。FreeBSD や Linux のカーネルドライバはカーネル開発者がコードレビューをしますが、Windows は WHQL(Windows Hardware Quality Labs)認証があるとはいえ、サードパーティドライバが比較的自由にカーネル空間で動作できます。つまりファームウェアの不一致に加えて、ドライバ自体のバグも混在します。
FreeBSD の設計思想との対比
こうして並べてみると、FreeBSD の設計方針が見えてきます。
Linux は「全部入り・自動」を目指した結果、問題が起きたときの対処の粒度が粗くなっています。Windows は「OEM 任せ」の部分が大きく、品質が均一ではありません。
FreeBSD は base system と ports/packages を明確に分離し、ユーザーが何をインストールするかを把握したうえで管理する設計です。「無駄がない・透明性が高い」反面、ユーザーに知識を要求します。drm-kmod を ports からビルドすることを好むのも、その透明性と制御性を活かした使い方といえます。
firmware が見つからない panic は確かに痛い経験ですが、原因が明確で bectl で切り戻せるという点では、ブルースクリーンで起動不能になり原因も不明という Windows の状況よりも、ずっと対処可能な設計になっています。
FreeBSD で panic したときに備え、BE の使い方はぜひ覚えておきましょう。
kernel が panic しても、パニックに陥らず、冷静に、git checkout する如く、boot menu で BE を選択して過去に戻しましょう!
