【組み込みLinux (2)】Device Driver について

今回はLinuxデバイスドライバ編です。

Linuxデバイスドライバ基礎

デバイスドライバを組み込む方法は2種類あります。

  • 静的にカーネルに組み込む (カーネル展開時に読み込む)
  • モジュールとして組み込んで動的に読み込む

Linuxではデバイスをファイルとして扱います。
/dev/* にデバイスに対して一意のメジャー番号が割り振りられます。
同一デバイスが複数つながれた場合はマイナー番号が付与されます。

静的に組み込んだ場合はカーネル展開にinit_module()が呼ばれます。
モジュールとして組み込んだ場合はinsmod (modprobe) / rmmod を叩いてinit_module()とcleanup_module()を呼んであげます。

  • indmod : デバイスドライバの組み込み
  • modprove : デバイスドライバの組み込み(依存ドライバ含む)
  • rmmod : デバイスドライバを外す
  • lsmod : インストール済みのデバイスドライバ一覧
  • modinfo : authorなどデバイスドライバの情報

ユーザメモリ空間(仮想メモリ)とカーネルメモリ(ドライバ)空間でデータを送受信するには, ファイル識別子(以下fd)を用います。
基本的にはユーザ空間からはシステムコールでのみカーネル空間にアクセスできます。
ユーザ空間は仮想アドレスで管理されているので,物理アドレスを知っているカーネル空間を経由 (mmap等)してアクセスする必要があるのです。
ここでいうアクセスとはカーネル空間に置かれているデータへのポインタではなく, データのcopyへのアクセスになります。
データサイズが大きい場合RTOSと比較すると, オーバーヘッドが発生するのは否めません。
ユーザ空間自体のプログラムに制限をかけることでOSの堅牢性を高めているとも言えます。

/* ex) read syscall */
dev_read( struct file* fd, char* buf, size_t count, loff_t* pos )

readはデータが読み込まれるまで待ってしまうので, select / pollなどで読み込み可能か事前に確認しておきましょう。

fdを引数にして, デバイスドライバの関数をシステムコール (カーネルとユーザプログラムのインターフェイス)で呼び出しますが, システムコールハンドラに対応している関数のみコールバックできます。

struct file_oerations fops =
{
  .open = dev_open;
  .read = dev_read;
  .write = dev_write;
}

サイズが大きいデータをやり取りしたい場合, デバイスドライバでDMA転送を使えばデバイス間で高速に転送できます。
DMAは一般的に転送元, 転送先の物理アドレスを確定させて転送を行いますが, LinuxのDMA-Driverは非連続な物理領域を集めるscatterlistという仕組みがあります。またCPU依存(DMAコントローラの差異)を避けるためにDMAEngineという汎用インターフェイスを用意しています。

さらに, LinuxではDMA転送を行うときには DMA Buffer Sharingという物理アドレスをポインタで要求・取得できるDMA Bufferを利用した共有メモリの仕組みがあります。

割り込みとポーリング

Linuxがペリフェラルデバイスを認識する方法は2つあります。

  • ハードウェア割り込み
  • ソフトウェア割り込み(ポーリング)

I/Oポートの周期的なポーリングにおいてポーリング周期はデバイスが状態変化を起こす時間の半分より小さい周期(サンプリング定理)で実装すれば良いことになります。でないと, I/Oイベントに反応できずにデータを取りこぼす可能性があります。

蛇足ですがUARTについて。
USBやIEEE1394などの比較的高速なものがあっても, RS232Cはまだまだ開発の現場だと重宝されてます。

デバイスドライバの場所

通常, デバイスドライバは以下のパスにあります。

/lib/modules/***/kernel/drivers

ここにある .koはinsmodなどでロードしなければメモリは消費しませんが, カーネルイメージには含まれています。
デスクトップ版なら以下を実行することで現在使っているモジュールだけのカーネル構成にでき容量を節約できます。

$ make localmodconfig

逆にできる限り多くの設定を有効にするには以下です。

$ make allmodconfig

Linux USBデバイスドライバ

USBデバイスドライバの作成は意外と難しかったりします。というのもホットプラグを実現するためにUSB Coreというサブシステムを介す必要があるのが理由のひとつです。

デバイス側から見て以下のような構成となってます。

構成
モジュール 機能
USB HostController USB全体を制御するデバイスドライバ
USB Core Linux カーネルの USB サブシステム。デバイスファイルリストを作成したりUSBデバイスドライバとデバイスのインターフェイスの役割
USB DeviceDriver 独自でつくる部分。[ 汎用デバイスでなければVenderIDとProductIDが必要 ]

このサブシステムを利用するときは組み込み機器の環境によってはカーネルの再構築が必要になる場合があります。

USBデバイスの挿入から通信, 抜取までの一連の流れは以下のようになります。

実装は通常のデバイスドライバの実装 + USB Coreが持つUSBデバイスとの通信機能の実装が必要となります。
具体的には USB CoreのURB (USB Request Block)機能を使います。
URBの種類は4つに分かれます。

// 代表的な2種類
// 双方向
int usb_control_msg (	
        struct usb_device *dev,
 	unsigned int  	pipe,
 	__u8  	request,
 	__u8  	requesttype,
 	__u16  	value,
 	__u16  	index,
 	void *  	data,
 	__u16  	size,
 	int  	timeout
);
// 単方向
int usb_bulk_msg (
	struct usb_device *  usb_dev,
 	unsigned int  	pipe,
 	void *  	data,
 	int  	len,
 	int *  	actual_length,
 	int  	timeout
);

次回は軽量HTTPサーバであるLighttpdを組み込む方法について書きます。