Wireless・のおと

サイレックスの無線LAN 開発者が語る、無線技術についてや製品開発の秘話、技術者向け情報、新しく興味深い話題、サイレックスが提供するサービスや現状などの話題などを配信していきます。

前の記事:「ギャンブル必勝法と情報量のはなし」へ

Linuxドライバのはなし

2015年8月31日 16:00
YS
ここのところ昔話やさまざまな話が続いていたので、現実的な話でも書いてみます。今回は Linux ドライバとプラグ&プレイについて短くまとめてみました。
Linux におけるデバイス検出・ドライバロードの仕組みはバージョンやコンフィギュレーションによって少しづつ違っていますが、今回の解説は Linux 3.10 あたりの構成を前提としています。

「ドライバー」とは何か?
Linux はモノリシック(Monolithic)・カーネル OS です。「モノリシック」というのは OS の中枢に必要となる全ての機能(ソフトウェア)が一種のサブルーチンとして、巨大な一個の実行ファイルに静的にリンクされていることを示します。ちなみにモノリシックの対語はマイクロカーネル(Micro Kernel)で、この場合は個々の機能が独立したプログラムになっており、サブルーチンコールではない何らかのデータ交換手段(IPC:Inter Process Communication)を用いて通信します。
Linux におけるドライバーもカーネルにリンクされるサブルーチンの一種です。ドライバーは特定のハードウェアリソース(例えば UART シリアルポートの /dev/ttymxc0)に対する一連の操作(例えば open, read, write, close, ioctl)をまとめたサブルーチンパッケージと考えることができます。
さて、Linux はモノリシックカーネルではありますが、システム起動後にファイルを読み込み、ダイナミックリンクの機能を用いて後付けで機能拡張することもできます(※註)。これをローダブル・カーネル・モジュール(LKM)と呼び、拡張子 *.ko のファイルとして実装されます。一方、あくまでモノリシックとしてドライバをカーネルのイメージ(vmlinux)に静的リンクしシステム起動時に全ての機能を読みこむこともできます。この場合、使用するドライバはカーネルの構成(Kernel Configuration)時に選択されていなければなりません。

(※註) ただし後付けのカーネルモジュールを使った場合も、モジュールとカーネルの通信はサブルーチンコールになります。カーネルとモジュールのコンパイル条件が異なると構造体の並びや引数の渡し方に食い違いが生じて致命的クラッシュを招きかねないため、カーネルとモジュールの整合を検証する vermagic と呼ばれる仕組みが実装されています。一方マイクロカーネル OS では IPC の通信仕様が共通であるかぎり、カーネルとモジュールのバージョンを統一する必要はありません。

カーネルモジュール
静的リンクされたモジュールはカーネルイメージ(vmlinux)の一部になりますが、動的リンクされるモジュールは拡張子 *.ko のファイルとしてファイルシステムの何処かに置き、必要なときにロードする形態を取ります。現在ロードされているモジュールの一覧は「lsmod」で表示されます。blogのシステムの仕様固定ピッチ設定できないので表示がガタガタになっていますがご容赦を。

# lsmod
Module                  Size  Used by
ov5642_camera          75119  0
mxc_v4l2_capture       22322  0
ipu_bg_overlay_sdc      4001  1 mxc_v4l2_capture
ipu_still               1663  1 mxc_v4l2_capture
ipu_prp_enc             4645  1 mxc_v4l2_capture
ipu_csi_enc             2841  1 mxc_v4l2_capture
ipu_fg_overlay_sdc      4877  1 mxc_v4l2_capture
ov5640_camera_mipi     21074  0
ov5640_camera          17959  0
ath9k                  76127  0
ath9k_common            1627  1 ath9k
ath9k_hw              361268  2 ath9k_common,ath9k
ath                    13650  3 ath9k_common,ath9k,ath9k_hw
mac80211              227200  1 ath9k
mxc_dcic                5334  0
cfg80211              176095  3 ath,ath9k,mac80211
evbug                   1476  0

カーネルモジュールを読みこむ基本コマンドは「insmod」で、引数として *.ko ファイルのファイル名(パス名)を指定します。

# insmod /lib/modules/3.10.53-1.1.0_ga+g496fbe0/kernel/drivers/net/wireless/ath/ath9k/ath9k.ko

insmod コマンドを使うかぎり、*.ko ファイルは何処に置いても構いません。しかしこの場合、デバイスの自動検出・対応ドライバの自動ロード(プラグ&プレイ)機能は使用できません。プラグ&プレイ機能を動作させるためには、該当するモジュールが「modprobe」コマンドで読み込み可能な状態になっていなければなりません。
modprobe は insmod とよく似ていますが、2つの点で大きく違います。insmod では「*.ko ファイルのファイル名」を引数として指定しますが、modprobe は「モジュール名」を引数とします。通常はモジュール名に拡張子 .ko を付けたものがファイル名になりますが、たまに違うものがあったりします(例えば Freescale i.MX280 の SDIO ドライバはファイル名 "mxs-mmc.ko" ですが、モジュール名は "mxs_mmc" だったりします)。

もう一つの違いは、複数のモジュールが相互に参照する依存関係にあったとき、insmod では「親」のモジュールから順々にロードしなければならないのに対し、modprobe では必要となる親モジュールを芋蔓式に判定して自動的にロードしてくれることです。

# modprobe ath9k
cfg80211: Calling CRDA to update world regulatory domain
cfg80211: World regulatory domain updated:
cfg80211:   (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)
cfg80211:   (2402000 KHz - 2472000 KHz @ 40000 KHz), (300 mBi, 2000 mBm)
cfg80211:   (2457000 KHz - 2482000 KHz @ 20000 KHz), (300 mBi, 2000 mBm)
cfg80211:   (2474000 KHz - 2494000 KHz @ 20000 KHz), (300 mBi, 2000 mBm)
cfg80211:   (5170000 KHz - 5250000 KHz @ 40000 KHz), (300 mBi, 2000 mBm)
cfg80211:   (5735000 KHz - 5835000 KHz @ 40000 KHz), (300 mBi, 2000 mBm)
cfg80211: Calling CRDA for country: JP
ieee80211 phy0: Atheros AR9280 Rev:2 mem=0xc1120000, irq=155
root@imx6qsabresd:~# cfg80211: Regulatory domain changed to country: JP
IPv6: ADDRCONF(NETDEV_UP): wlan0: link is not ready
cfg80211:   (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)
cfg80211:   (2402000 KHz - 2482000 KHz @ 40000 KHz), (N/A, 2000 mBm)
cfg80211:   (2474000 KHz - 2494000 KHz @ 20000 KHz), (N/A, 2000 mBm)
cfg80211:   (4910000 KHz - 4990000 KHz @ 40000 KHz), (N/A, 2300 mBm)
cfg80211:   (5030000 KHz - 5090000 KHz @ 40000 KHz), (N/A, 2300 mBm)
cfg80211:   (5170000 KHz - 5250000 KHz @ 40000 KHz), (N/A, 2000 mBm)
cfg80211:   (5250000 KHz - 5330000 KHz @ 40000 KHz), (N/A, 2000 mBm)
cfg80211:   (5490000 KHz - 5710000 KHz @ 40000 KHz), (N/A, 2300 mBm)

こんな便利な modprobe ですが、modprobe を適切に動作させるためには幾つかのルールを守らなければなりません。まず、modprobe で扱うモジュールは全て規定のディレクトリに収められていなければなりません。通常、これは /lib/modules/<KernelVersion> 下に置かれています。
しかし *.ko ファイルを /lib/modules/<KernelVersion> 下に置くだけではまだ不十分で、/lib/modules/<KernelVersion> 下のカーネルファイルの一覧が modules.dep ファイル(およびそのバイナリ版である modules.dep.bin) に登録されていなければなりません。この登録は「depmod」コマンドを /lib/modules/<KernelVersion> 下で実行することによって行われます(※註)。

(※註) つまり、/lib/modules 下に何らかの変更(追加、削除、置き換え)をするたびに depmod を実行しなければ modprobe の動作は保証されない、ということです。デスクトップ Linux のセルフ環境では make modules_install などのスクリプトによって /lib/modules への *.ko ファイルコピーと depmod の実行が自動で行われたりしますが、組み込み系のクロス開発環境では誰がどこでどうやって *.ko をコピーし depmod を実行するかは環境によってまちまちなので、何も考えずに手作業で *.ko だけ追加コピーして「動かない」というトラブルになりがちです。また depmod は「一覧を作る」だけが仕事で同名のモジュールを持つファイルが複数あることの警告なども出してくれないので、新しいドライバをインストールしたつもりが modprobe は残っている古いドライバを使い続けていた、なんてトラブルも割とよくあります。


プラグ&プレイ

USB や SDIO のように稼動中の抜き差し(ホットプラグ)に対応したバスの場合、新しいデバイスの挿入検出ごとに UEVENT と呼ばれるカーネルイベントが発生します。PCI のように稼動中の抜き差し不可能な場合でも、起動直後のバス探索(プローブ)によってデバイスが検索され、やはり UEVENT が発生します。
UEVENT には検出されたデバイスを示す ID 文字列が付加され、これをデバイス・エイリアスと呼んでいます。デバイス・エイリアスは "pci:v0000168Cd0000002A" のような文字列で、小文字がカテゴリを、大文字が数値を示しています。この場合は "pci:" が PCI デバイスであること、"v0000168C" がベンダー ID=0x168C (Qualcom Atheros) であること、d0000002A がデバイス ID=0x2A (AR9280 チップセット) であることを意味しています。
Linux カーネル自体は UEVENT によるドライバの自動ロード機能を持っておらず、これはユーザー空間で稼動するデーモンプロセスによって実装されます。UEVENT の実装も Linux の世代によって異なりますが、3.10 前後の Linux では udevd と呼ばれるデーモンが処理している場合が多いです(より新しい Linux では systemd というデーモンが稼動していますが、udevd は systemd に取り込まれた形でほぼそのまま生き残っています)。
udevd は通常 /lib/udev 下に格納されており、/lib/udev/rules.d 下に格納された「ルールファイル」に基づいて UEVENT を処理します。udevd の仕事は外部記憶メディアの自動マウントやネットワーク設定の自動適用など多岐に渡りますが、今回はドライバの自動ロードに限って解説を続けます。

デバイス検出に伴うドライバのロードは通常、rules.d/80-drivers.rules で処理されます。この rules ファイルの中身は udev のバージョンによって、あるいは Linux のディストリビューションによって微妙に異なりますが、おおむね下記のような内容が書かれているはずです。

ACTION=="remove", GOTO="drivers_end"

ENV{MODALIAS}=="?*", RUN{builtin}="kmod load $env{MODALIAS}"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN{builtin}="kmod load tifm_sd"
SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN{builtin}="kmod load tifm_ms"
SUBSYSTEM=="memstick", RUN{builtin}="kmod load ms_block mspro_block"
SUBSYSTEM=="i2o", RUN{builtin}="kmod load i2o_block"
SUBSYSTEM=="module", KERNEL=="parport_pc", RUN{builtin}="kmod load ppdev"
KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", RUN{builtin}="kmod load sm_ftl"

LABEL="drivers_end"

ドライバのロードは RUN{builtin}="kmod load $env{MODALIAS}" によって処理されます。udev の少し古いバージョンでは RUN+="/sbin/modprobe -bv $env{MODALIAS}" になっているかもしれません。前者は udevd 内蔵のモジュールロード機能(modprobe と互換)、後者は外部の modprobe を使ってモジュールをロードすることを指示しています。いずれの場合も引数は $env{MODALIAS} となっており、ここに環境変数として UEVENT から渡されたデバイス・エイリアスが当て嵌められます。


modinfo とエイリアス
上で「modprobe はモジュール名を引数として動作する」と書きましたが、実は modprobe はデバイス・エイリアスを引数としても動作します。AR9280 に対応するドライバが ath9k.ko だとしたら、"modprobe ath9k" と書いても "modprobe pci:v0000168Cd0000002A" と書いても同じ動きになります。
これにも仕掛けがあって、/lib/modules/<KernelVersion>/modules.alias にエイリアスとモジュール名の対応関係がずらりと書かれており、modprobe はこれを参照してデバイス・エイリアスからモジュール名を逆引きし、そのモジュール名を今度は modules.dep から参照して該当するファイル名および依存情報を調べています。modules.alias も modules.dep 同様バイナリ版の modules.alias.bin がペアになっており、やはり depmod コマンドによって生成されます。

これらの情報...ドライバ毎の対応するエイリアスや依存モジュールの情報は、ドライバファイルの *.ko 自身に埋め込まれています。それを調べるのが「modinfo」コマンドで、例えば ath9k.ko に対して modinfo を実行すると以下のような結果が表示されます。

$ modinfo ath9k
filename:       /lib/modules/3.11.10-301.fc20.i686/kernel/drivers/net/wireless/ath/ath9k/ath9k.ko
license:        Dual BSD/GPL
description:    Support for Atheros 802.11n wireless LAN cards.
author:         Atheros Communications
alias:          platform:qca955x_wmac
alias:          platform:ar934x_wmac
alias:          platform:ar933x_wmac
alias:          platform:ath9k
alias:          pci:v0000168Cd00000036sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000037sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000034sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000034sv000010CFsd00001783bc*sc*i*
alias:          pci:v0000168Cd00000034sv000014CDsd00000064bc*sc*i*
alias:          pci:v0000168Cd00000034sv000014CDsd00000063bc*sc*i*
alias:          pci:v0000168Cd00000034sv0000103Csd00001864bc*sc*i*
alias:          pci:v0000168Cd00000034sv000011ADsd00006641bc*sc*i*
alias:          pci:v0000168Cd00000034sv000011ADsd00006631bc*sc*i*
alias:          pci:v0000168Cd00000034sv00001043sd0000850Ebc*sc*i*
alias:          pci:v0000168Cd00000034sv00001A3Bsd00002110bc*sc*i*
alias:          pci:v0000168Cd00000034sv00001969sd00000091bc*sc*i*
alias:          pci:v0000168Cd00000034sv000017AAsd00003214bc*sc*i*
alias:          pci:v0000168Cd00000034sv0000168Csd00003117bc*sc*i*
alias:          pci:v0000168Cd00000034sv000011ADsd00006661bc*sc*i*
alias:          pci:v0000168Cd00000034sv00001A3Bsd00002116bc*sc*i*
alias:          pci:v0000168Cd00000033sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000032sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000032sv0000105Bsd0000E075bc*sc*i*
alias:          pci:v0000168Cd00000032sv00001A3Bsd00002152bc*sc*i*
alias:          pci:v0000168Cd00000032sv00001A3Bsd00002126bc*sc*i*
alias:          pci:v0000168Cd00000032sv00001A3Bsd00001237bc*sc*i*
alias:          pci:v0000168Cd00000032sv00001A3Bsd00002086bc*sc*i*
alias:          pci:v0000168Cd00000030sv*sd*bc*sc*i*
alias:          pci:v0000168Cd0000002Esv*sd*bc*sc*i*
alias:          pci:v0000168Cd0000002Dsv*sd*bc*sc*i*
alias:          pci:v0000168Cd0000002Csv*sd*bc*sc*i*
alias:          pci:v0000168Cd0000002Bsv*sd*bc*sc*i*
alias:          pci:v0000168Cd0000002Asv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000029sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000027sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000024sv*sd*bc*sc*i*
alias:          pci:v0000168Cd00000023sv*sd*bc*sc*i*
depends:        ath9k_hw,ath9k_common,mac80211,ath,cfg80211
intree:         Y
vermagic:       3.11.10-301.fc20.i686 SMP mod_unload 686
signer:         Fedora kernel signing key
sig_key:        30:6A:44:54:85:F5:93:A7:D1:76:66:ED:6E:88:70:81:51:C7:51:D4
sig_hashalgo:   sha256
parm:           debug:Debugging mask (uint)
parm:           nohwcrypt:Disable hardware encryption (int)
parm:           blink:Enable LED blink on activity (int)
parm:           btcoex_enable:Enable wifi-BT coexistence (int)
parm:           enable_diversity:Enable Antenna diversity for AR9565 (int)

"alias:" には対応するデバイス・エイリアスがずらりと列挙されており、depmod はこれを抽出して modules.alias ファイルを作っています。また "depends:" には ath9k.ko の動作に必要となる親モジュールの一覧が列挙されており、depmod はこれを抽出して modules.dep ファイルを作っています。


まとめ

まとめると、Linux におけるデバイス検出とドライバの自動ロードは

(1) デバイスのホットプラグ、ないしは起動時のバス・プローブでデバイスが検出され、デバイス・エイリアス文字列を引数とした UEVENT が発生する。

(2) udevd などのシステムデーモンが UEVENT を受信し、エイリアス文字列を引数として modprobe コマンド(ないしは互換機能である内蔵の kmod load)を実行する。

(3) modprobe は /lib/modules/<KernelVersion>/modules.alias からエイリアスを検索する。エイリアスが見つかった場合は、対応するモジュール名を引数として modprobe を実行する。

(4) modprobe は /lib/modules/<KernelVersion>/modules.dep からモジュール名を検索する。モジュール名が見つかった場合は依存モジュール一覧を調べ、まだロードされていなかった場合は更に親モジュール名を引数として modprobe を実行する。

(5) 依存モジュールがロード済みとなった段階で、モジュール名に対応するモジュールファイル名を modules.dep から抽出し、insmod コマンド(ないしは互換機能)を実行して該当ファイルを読み込む。

という手順によって行われていることになります。Linux で「プラグ&プレイが動かない」というトラブルはこの中の何処かで齟齬が生じていることによって引き起こされます。逆に言えば、Linux のプラグ&プレイが動作していない場合はこのステップを逆に辿ってゆくことがトラブル調査の基本になります。

- insmod をでファイル名を直接指定してドライバロードできるか?
- modprobe でモジュール名を指定してドライバロードできるか?
- modprobe でデバイスエイリアスを指定してドライバロードできるか?
- デバイスは検出されているか?(/sys/bus などを利用)
- デバイス検出の UEVENT は発生しているか?(dmesg, udevadm などを利用)
- 検出されたデバイスのエイリアスは modules.alias に登録されているか?

比較的単純なケースはこれで対処できますが、もっと混み入った事情で動作しないケースもあったりするのが Linux の世界ではあります。今回こんな記事を書いたのも、実は「特定の udev バージョンで特定の条件を持つドライバがロードできないことがある」という厄介な問題を調べたときの資料の再利用だったりします。



次の記事:「Bluetoothのセキュリティのはなし」へ

最新の記事

カテゴリ

バックナンバー