Linux kernel の見取り図 †
標準ライブラリ †
Linux kernel で提供される標準ライブラリはおおよそアプリケーション向けの glibc に近い機能が提供されています。加えてビット操作と kernel 内ならではのアルゴリズム群があります。アルゴリズム群は「こんなのも有るのか」程度に知っておくと良いでしょう。アプリケーション向けに作られたライブラリが移植されていることもあるので、欲しいと思ったら Linux kernel ソースコード検索 から探してみるのも良いでしょう。
要注意なことは、アプリケーションプログラムでは普通に除算 '/'、剰余 '%' 演算子を用いた式を long long 型に対して書けば計算可能なのに対し Linux kernel 内では linux/math64.h からインクルードされる関数群を使用して関数呼び出し(あるいはマクロ呼び出し)の形で計算する必要があることです。
リンクリスト †
双方向リンクリストを操作するマクロです。リストを辿るループ処理を構成する list_for_each_entry() などの巧妙なマクロが定義されています。
アトミック操作 †
整数とビットに対するアトミック操作の機能群です。Tasklet, Work Queue などを含み、複数のスレッドや割り込みコンテキストと共有する変数、お互いの同期を取るための状態変数を操作するために使用します。ソースコード中で a++ や a |= FLAG_MASK といった式を書いたときに 変数 a は複数スレッドや割り込みコンテキストから参照・更新されるのか常に気にする必要が有ります。もし、参照・更新されるのなら spin_lock やアトミック操作が必要である可能性が高いです。
バリア指示・同期 †
バリア指示 barrier() とバリア同期 mb(), rmb(), wmb() の関数またはマクロ定義です。バリア指示はコンパイラに対して、barrier() を超えてメモリアクセス順を変更しないようにします。バリア同期は同期関数の前までに行ったメモリ操作 mb(), メモリ読みだし rmb(), メモリ書き込み wmb() を済ませるよう指示します。インラインアセンブラなどによってロート・ストアキューまたはキャッシュに保留されたトランザクションをメモリに反映します。Documentation/memory-barriers.txt を参照して下さい。
バリア同期を行ったのにデバイスの制御が不完全になっていると思われる場合、(1) レジスタの読み書きにリカバリータイム(連続アクセスする場合の待ち時間)が規定されていないか、(2) バスブリッジにトランザクションが滞留していないか 確認してください。さらに他にも原因はあるかもしれません。ここで (2) の場合について触れます。プラットホーム(SoC 内部に構成された回路ブロックやバス、基板・バックプレーン・インターコネクトで接続されたプロセッサ、チップセット、バスブリッジなどで構成されるアーキテクチャ)によっては各回路(特にバスブリッジやインターコネクト)のリクエストキューにバストランザクションがバリア指示・同期を行っても滞留する場合もあります。プラットホームのデータシートを参照して意図通りにバリア指示・同期ができているか確認して下さい。
滞留したバストランザクションの掃き出し
プラットホームのデータシートを参照すれば何らかの方法でバスブリッジに滞留したトランザクションを吐き出す(完了するまで待つ)方法が書かれていると思います。方法が見つからない場合、制御しようとするデバイスのバージョンレジスタ、コンフィギュレーションレジスタなどデバイスの内部状態を変えないレジスタを読みだしてみて、滞留したトランザクションを吐き出せないか実験してみてください。先行した書き込みを追い越して読み出しのトランザクションが実行されることは恐らく無いと思われます。あったら、バスあるいはバスブリッジの設計が破綻しています。
排他制御 †
複数の処理を一貫して行いたい場合に使う関数とマクロ群です。Spin Lock は複数のバリエーションがあり、割り込みコンテキストと共に使える機能もあります。ロックを獲得できるまで CPU が Spin (空走ループ)します。短時間で済む処理をロックして行うのに適しています。空走時間が長くなる場合 CPU の処理能力を無駄に消費します。
Semaphore, Mutex は呼び出したプロセスやスレッドなどを中断して他に実行を譲れる状況で使用します。割り込みコンテキストの中から使うことは出来ません。ロックを獲得できないならば、他に実行を譲ります。
同期制御、状態変化待ち †
条件が成立するまで待つ機能、完了を待つ機能があります。別スレッドや work queue に処理を任せたあと完了を待ったり、割り込みが発生を条件に処理を進める場合に使用します。
割り込みハンドラ登録・設定 †
デバイスから来る割り込みを受ける関数を登録するための定義群です。ドライバやデバイスが外された時は登録抹消します。割り込みコントローラの制御は SoC ベンダーあるいはボードベンダーによって実装されています。割り込みハンドラに必要最低限の処理は割り込み要因を解消することです。解消しない場合は割り込み処理が再割り込みによって永遠に継続したり、2 度目の割り込みが発生しないなどの事態になります。
時間待ち、タイマー †
制御対象のデバイスはプロセッサの処理速度に比べ動作が遅いことがあります。コマンド発行間隔、要求してから結果が得られるまでの待ち時間が必要な場合が有ります。Sleep, Delay, Timer はこれらのタイミングを合わせるための機能です。注意点はこれらの待ち時間は長くなる方向でぶれます。平行して隣接する機能ブロックを操作している場合、処理や制御の重なりで思わぬ挙動を示す場合が有ります。
時刻 †
kernel 内で刻まれている時刻を取得する機能群です。jiffies はプリエンプション周期である 1/HZ 秒単位 で進む時刻です。ミリ秒オーダーの精度です。より高精度の時刻を必要とする場合は、local_clock(), sched_clock() を使用して下さい。これらは kernel が起動されてから刻まれる時刻です。現実世界の時刻と相対的な時間差があります。CPU が完全に停止した suspend 中には刻まれず止まっています。
メモリ確保 †
アプリケーションで使用している malloc(), free() の代わりになる機能です。動的なメモリ確保を必要とする場合、ほぼ kzalloc(), kfree() の 2 つで十分に対応できます。
生成したデバイスコンテキスト、デバイスに対するコマンドパケットなどの生存期間はデバイスが突然外されたり、コンテキストが複数のリンクリスト等に属する場合、特定の操作に合わせた単純な allocate - free の関係では扱いきれない場合が有ります。リファレンスカウンタ (ドキュメントは Documentation/kref.txt、ヘッダファイルは linux/kref.h) が使えないか、検討して下さい。
仮想-物理アドレス・マップ、DMA 転送、コピー †
おそらく DMA 転送を主導するようなドライバを書く機会は少ないと思います。ほぼこのような処理は SoC デバイスメーカーや開発プラットホームを作ったメーカーが提供しています。最も使うのは user space - kernel copy だと思います。ioctl(), read(), write() に対応する処理を実装すると、アプリケーションプログラムへ(から)データを転送する処理が伴います。このデータ転送は(実質的な実装がそうであっても) memcpy() を使ってはいけません。
機能単位 | 主なヘッダやソース | 備考 |
user space - kernel copy | asm/uaccess.h linux/uaccess.h | ドキュメントは User Space Memory Access です。asm/uaccess.h は linux/uaccess.h で include されます。copy_to_user(), copy_from_user() などはアーキテクチャに最適な実装がされることがあります。ioctl() の実装などで構造体の各メンバーを順にアクセスする場合は、構造体全体のアクセス可能性を access_ok() で確認してから進めるのが良いでしょう。 |
Virt - Phy map convert | asm/io.h linux/io.h mach/hardware.h | ドキュメントは Public Functions Provided(題名が変ですが) です。asm/io.h は linux/io.h で include されます。virt_to_phys(),phys_to_virt() などはアーキテクチャやプラットホームに最適な実装がされることがあります。 ioremap(), iounmap() などを使って物理アドレスと仮想アドレスの対応付けをします。一部のプラットホームでは内蔵された機能ブロックの仮想アドレス割り付けを単純化していて、軽量な実装の IO_ADDRESS() マクロなどを経由して物理アドレスから仮想アドレスへ変換出来るよう便宜が図られています。arch/processor/ 以下に格納されたプラットホーム毎の初期化処理やデバイス登録処理を読んでみて機能ブロックに対する仮想アドレスの扱いかたを把握しておくと良いでしょう。 |
DMA mapping, Address convert, Scatter Gather | asm/dma-mapping.h asm-generic/dma-mapping-common.h | ドキュメントは Documentation/DMA-API.txt, Documentation/DMA-API-HOWTO.txt です。asm/dma-mapping.h, asm-generic/dma-mapping-common.h は linux/dma-mapping.h で include されます。 よく使うのは dma_map_single(), dma_unmap_single(), dma_map_page(), dma_unmap_page(), dma_map_sg(), dma_unmap_sg() です。アーキテクチャ、プラットホームによっては DMA アドレスと物理アドレスが違っています。phys_to_dma(), dma_to_phys() 関数が用意されています。 |
メモリ・マップド・デバイス・アクセス †
SoC 内蔵の機能ブロック、PCI 接続のカードに乗っているデバイスはメモリにマップされたレジスタやメモリ空間があります。これらをアクセスするために用意された関数、あるいはマクロ群です。C 言語の '*' (間接演算子) を使ったアクセスは禁止されていません。しかし、関数やマクロには volatile を付加したり、メモリバリアを行ったりデバイスをアクセスする作法として必要な処理が盛り込まれています。
スレッド・軽量処理 †
「ユーザーランドの動作と独立して kernel 内でデバイスの変化に合わせデバイスを制御する。」、「デバイスが起こした割り込みに対応する。」等の処理をするためスレッド(kthread)、work queue、tasklet を利用できます。割り込み処理は request_irq() で登録した割り込みハンドラ(関数)で行います。割り込みハンドラはアトミックなので(他のスレッドやプロセスに実行を譲らないので)使用できる kernel 内の API は限られます。ハンドラは割り込み要因をクリアする事と、割り込み時に必要な処理を起床させることに専念し軽量な実装にして、割り込み発生時に必要な処理は kthread, work queue で行うのがドライバで多く見られる実装です。
スレッドか work queue か?work queue を使う方が実装量が少なく簡単です。たとえばデバイスのステータスを読み取り、ドライバで確保したデバイスコンテキストに格納するだけのような単純な処理であれば work queue を使うのが適切でしょう。ただし、work queue は手軽な代わりに複雑な処理を盛り込もうとすると、難易度が高くなります。たとえばワークを実行する関数内で再び自らを work queue に投入する様な処理可能です。しかし、何かの事象が発生し別のコンテキストが work queue に投入する処理を行う場合もあることを忘れないで下さい。
ポーリング処理が必要なデバイスであれば、kthread を使うのが適切でしょう。thread 内で wait_event_timeout() あるいは類似の関数を使用し、事象を待ちつつタイムアウトを併用して定期的なポーリングを実現します。
デバイスを制御するのに複数のステップを必要とする場合も、kthread を検討して下さい。スレッドとして動作する関数にステートマシンを実装して対応できます。
機能単位 | 主なヘッダやソース | 備考 |
kthread | linux/kthread.h | 主に使う関数は kthread_create(), kthread_stop(), kthread_should_stop() です。すこし高度な使い方が Internal Functions として書かれています。 kthread_create() で作成した thread は wake_up_process() で起床すするまでは止まった状態です。kthread_stop() は KTHREAD_SHOULD_STOP フラグを 1 にするだけです。kthread_should_stop() で KTHREAD_SHOULD_STOP フラグの値を読みます。 kthread はアプリケーションで使われる pthread と違い、終了待ち機能 pthread_join に相当するを提供していません。必要であれば実装して下さい。 kthread の中では copy_to_user(), copy_from_user() は使えません。open 処理の中で kthread_create() を使ったとしても、ユーザーランドの仮想アレス割り付けは kthread に継承されません。 |
work queue | linux/workqueue.h | ドキュメントは Workqueues and Kevents と Documentation/workqueue.txt です。API が多くあるうち、良く使用するのは DECLARE_WORK(), INIT_WORK(), INIT_WORK_ONSTACK() と schedule_work(), cancel_work_sync() です。独自に work queue を作成する場合、加えて alloc_workqueue(), destroy_workqueue(), queue_work() です。 work queue と delayed work を一つの work queue で併用する興味深い実装が drivers/ata/libata-sff.c に見られます。 schedule_work() を使って system work queue (system_wq) を使う場合は要注意です。work queue に投入されたワーク work_struct は順次実行されます。他のドライバ、モジュールも system work queue にワークを投入します。投入したワークがどの程度時間が経過してから実行されるのかは、先行して投入されたワークの実行時間に依存します。自分が投入したワークの後に続いたワークは、自分のワーク実行に続けて実行されます。ワークの処理時間が長い場合、(見も知らない)お互いの実行時間干渉に注意が必要です。 |
tasklet | linux/interrupt.h | ヘッダファイルは linux/interrupt.h です。この中の tasklet_init(), tasklet_schedule(), tasklet_kill() をはじめとする関数群です。tasklet で実行される関数は softirq (TASKLET_SOFTIRQ) コンテキストで実行されます。msleep() の様に実行が他のスレッドに移る可能性がある関数は tasklet 関数の中から使えません。tasklet_schedule() で実行を予定すると即座に実行されるか、次のタイムティック(プリエンプション)で実行されます。ただし、Documentation/kernel-per-CPU-kthreads.txt の TASKLET_SOFTIRQ にあるように多用すると、他のドライバの割り込み応答時間が伸びる方向でぶれを生じやすくなります。ぶれを抑えるために work queue を使用する様に推奨されています。 |
接続切断通知 †
デバイスの状態変化を通知する uevent とコネクタの状態変化を通知する External Connector (extcon, Android においては switch) が有ります。
uevent は struct device のメンバーに含まれている (struct kobject) kobj 構造体を通してデバイスの状態を通知します。
External Connector (extcon) は Android 向けに拡張された Linux で Switch として実装された class を取り込んだ物です。組み込み機器向けの機能です。主にコネクタにプラグが「接続された|切断された」事象を伝えるために使われます。キーボードに分類されないようなボタンやドアスイッチの状態を伝達するためにも使えます。
リファレンスカウンタ †
kernel API の中で *_alloc(), *_get(), *_put() のという接尾辞が付いた API の組の根幹をなすライブラリです。間接的に kobject, kmap を通して使われることもあります。接尾辞は多少の変化があります。ほぼ *_alloc() で参照カウンタ 1 でインスタンスを生成します。*_get() で参照カウンタを +1、*_put() で参照カウンタを -1 します。参照カウンタが 0 になったら、kref_put() の引数に指定された関数 *_release() が呼び出され、多くの場合インスタンスを開放(メモリから削除)する処理をします。
Kernel Object †
機能単位 | 主なヘッダやソース | 備考 |
KObject, Kset | linux/kobject.h | kobject(kernel object) のインスタンスを直接生成する機会は殆ど無いと思います。kobject を利用する場面は、kobject を参照するポインタを使って /sys (sysfs) の下にあるデバイスやドライバのノードに特殊なノードを作ったり、uevent を発行するのが主な場面になるでしょう。 |
ノード形成 †
典型的なデバイスドライバは read(), write(), ioctl(), poll() などを通してデバイスをアクセスできるようにノードを作ります。伝統的かつ基本的な方法は character device または block device として VFS に {major, minor} 番号の組として表現したノードを登録します。登録はクラスデバイスとして登録したときにクラスドライバが代行する場合も有りますし、作成したデバイスドライバが自らする場合もあります。ユーザーランドから mknod コマンド(system call) で {major, minor} 番号の組に対応するデバイスパスを作成し、open, close できるようにします。
新しい方法、あるいは比較的軽量な方法として sysfs に(/sys 以下のノード)にデバイスの状態を read, write するためのノードを作成する方法があります。ノードの _show, _store メソッドをテキストで入出力する「気が利いた」実装にすれば、コマンドラインで cat や echo コマンドを使ってデバイスの状態を確認したり、設定することができます。
機能単位 | 主なヘッダやソース | 備考 |
VFS(character) | linux/fs.h linux/cdev.h | ドキュメントは Char devices です。alloc_chrdev_region(), register_chrdev(), unregister_chrdev_region(), cdev_alloc(), cdev_init(), cdev_add(), cdev_del() 辺りから使用例を探ると良いでしょう。 |
VFS(block) | linux/fs.h linux/genhd.h | ドキュメントは Block Devices です。register_blkdev(), unregister_blkdev(), blk_register_region(), blk_unregister_region(), alloc_disk(), put_disk(), add_disk(), del_gendisk() 辺りから使用例を探ると良いでしょう。 |
SCSI | scsi/scsi.h scsi/scsi_host.h scsi/scsi_driver.h scsi/scsi_device.h | block device として登録するならば SCSI device として登録した方が良い場合もあります。SCSI 周りは記述が多くなるので別記検討中です。 |
procfs | linux/proc_fs.h | procfs は任意のディレクトリ構造が作りやすいファイルシステムです。積極的に推奨されないせいもあり、詳細なドキュメントがありません。proc_mkdir(), proc_create(), remove_proc_entry(), proc_remove() の使用例を参考に使って見て下さい。 procfs (と debugfs)は linux/seq_file.h と共に使われることが多いです。seq_file の ドキュメントは Documentation/filesystems/seq_file.txt に有ります。ノードを read() するとテキストが得られるようにしたい場合に便利な実装です。バイナリも seq_write() で対応できます。 |
sysfs | linux/sysfs.h linux/device.h linux/moduleparam.h | デバイスに属性を付与する DEVICE_ATTR() は linux/device.h にあります。ドキュメントは Documentation/driver-model/device.txt の Attributes 節です。 module_param() は linux/moduleparam.h にあります。kernel boot parameter からスタティックリンクされたモジュールの module_param() に値を渡すことができます。Documentation/kernel-parameters.txt を参考にして下さい。 |
debugfs | linux/debugfs.h | ドキュメントは Documentation/filesystems/debugfs.txt と The debugfs filesystem です。ディストリビューター、SoC メーカー、ボードメーカーから提供された環境によっては debugfs が /sys/kernel/debug にマウントされていないことがあります。debugfs を使うには Documentation/filesystems/debugfs.txt を参考にマウントする必要があります。 名前の通りデバッグ目的のファイルシステムです。アプリケーション向けの API として常用したい場合は、sysfs か procfs へ移行するかデバイスアクセス用のノードを作成することを進めます。 |
モジュールロード、アンロード、シンボル解決 †
恐らくデバイスドライバ自身がここにある API を使うことは稀だと思います。insmod, rmmod, modprobe をしたときに問題が起きた場合、何が起きているのか追跡するために参照することが多いでしょう。シンボル解決機能は興味深い機能の一つでしょう。
基本的なドライバ †
/dev/null, /dev/zero, /dev/full はハードウエアとして存在するデバイスのドライバではありません。しかし、参考になります。機能が単純なため struct file_operations のメソッド(関数テーブル)に登録する read, write といったデバイスドライバの基本的な機能実装をどのようにすれば良いか見通し良く理解できる実装になっています。
機能単位 | 主なヘッダやソース | 備考 |
Null, Zero, Full, Mem drivers | drivers/char/mem.c | /dev/null, /dev/zero, /dev/full, /dev/mem の実装です。単純なキャラクタ型デバイスを実装したドライバです。単純ながら興味深い実装になっています。 |
基本的なファイルシステムノード †
select(poll), ioctl を実装するのに参考になるソースを上げておきます。pipe, eventfd はデバイスから読み出すデータをキューイングする方法の参考にもなるでしょう。
機能単位 | 主なヘッダやソース | 備考 |
pipe | fs/pipe.c | poll(select)の実装、ioctl FIONREAD の実装 は単純な挙動で特別なデバイスも無い環境でも試せるので参考になるでしょう。アプリケーション向けのドキュメントは man 7 pipe です。 |
eventfd | fs/eventfd.c | eventfd, signalfd, timerfd それぞれで system call を実装し、file descriptor を user space API として提供しています。独自の file descriptor を使用する API を必要としているならば読んでみるのも良いでしょう。アプリケーション向けのドキュメントは man eventfd です。 |
signalfd | fs/signalfd.c | アプリケーション向けのドキュメントは man signalfd です。 |
timerfd | fs/timerfd.c | アプリケーション向けのドキュメントは man timerfd です。 |
デバイス登録 †
Linux ではデバイスとドライバをそれぞれ管理しています。システムに存在するデバイスの管理と、システムに存在するドライバの管理をしています。登録されたデバイスとドライバは drivers/base を核として対応付けされます。起動処理時の特殊な状況が終わった後の対応付けは device_attach(), bus_for_each_drv() と driver_attach(), bus_for_each_dev() で行われています。対応付けの処理によりデバイスとドライバのどちらが先に存在しても(後から付け足されても)デバイスに対して適合するドライバを見つけ probe 処理が始まります。
組み込み開発で良く扱うバスとそこに接続するデバイスの登録処理を挙げます。platform(SoC チップ内蔵機能、基板上でメモリ、IO 空間に固定的に割り付けられた周辺回路), I2C, SPI バス接続デバイスは固定された構成としてデバイスを登録する必要があります。PCI, USB, MMC(SD card slot) の様に動的な接続・切断に合わせてデバイスを登録・削除する処理が既に実装されている場合もあります。
機能単位 | 主なヘッダやソース | 備考 |
platform | linux/platform_device.h | platform_device_register(), platform_device_unregister() にてプラットホーム・デバイスを登録、削除します。プラットホームデバイスは SoC に内蔵された IP ブロックが主に対象となります。ただし、suspend、resume、接続、切断 動作に関して何らかの上流バスの支配あるいは管轄下にある場合は、そのバスのデバイスとして登録します。 |
I2C | linux/i2c.h | I2C 関連の基礎的な用語 Algorithm, Adapter, Driver, Client については Documentation/i2c/summary にて解説されています。全般的なドキュメントは Documentation/i2c/ 以下のファイルと API の解説 Documentation/DocBook/device-drivers/i2c.html を参照してください。 多くの場合 i2c_register_board_info() を使ってプラットホームの初期化処理にてデバイスを登録します。動的な登録と削除は i2c_new_device(), i2c_unregister_device() で行います。 |
PCI | linux/pci.h | PCI 接続のデバイス登録は SoC メーカーあるいは開発環境を提供したメーカーがすでに初期化処理の中で行うよう実装しているはずです。pci_common_init(), pcibios_scan_root(), pci_bus_add_devices() 辺りからコードを追跡して確認してください。それでも意図して登録する場合は pci_bus_add_device(), リファレンスカウンタを減らす pci_dev_put() の用例を参考にしてください。 |
SDIO | linux/mmc/host.h | SDIO デバイスは自動でデバイスが登録されます。登録の流れは mmc_detect_change(), (delayed work を挟み) mmc_rescan(), mmc_rescan_try_freq(), mmc_attach_sdio(), mmc_attach_sd(), mmc_attach_mmc(), mmc_add_card() の様になります。 |
SPI | linux/spi/spi.h | ドキュメントは Serial Peripheral Interface です。spi_register_board_info() を使ってプラットホームの初期化処理にてデバイスを登録します。動的な登録は spi_alloc_device(), spi_add_device() で行います。spi_alloc_device(), spi_add_device() の流れで登録した SPI デバイスはリファレンスカウンタを減らす spi_dev_put() で削除します。 |
USB | linux/usb.h linux/usb/hcd.h | 接続処理(デバイス登録)は自動で行われます。usb_new_device(), hub_port_connect(), usb_hub_create_port_device() とその呼び出し元のコードを探索してみてください。 |
ドライバ登録 †
ドライバ登録はデバイス登録と対になります。デバイス登録のところに書いたようにどちらを先に登録してもデバイスとドライバは対応付けされます。
クラスデバイス登録 †
クラスデバイスは種類別に分類されたデバイスです。種類は発揮できる機能により決まります。たとえば LED (linux/leds.h) は「発光」する種類になります。前出の「デバイス登録」でいうデバイスは回路的な機能ブロックやバスに接続されたチップを意味しています。どんな機能かという情報は含まれていません。
ドライバにより機能ブロックやチップが確かに存在し、少なくとも初期化に成功する程度に動作すると確認できたならば、デバイスを操作するためのノードを作ります。ノードは独自に作る方法と、クラスデバイスとして(分類可能であれば)登録しクラスデバイスのコアモジュールにノードを作らせる方法があります。コアモジュールは汎用的な API を提供しているでしょう。
汎用的な API は複雑に見えるかもしれません。その複雑さを追うと、kernel 内での連携、アプリケーションに対する便利なアクセス方法、汎用 API を使った実用的なアプリケーションも見つかるでしょう。多少の難しい学習をすれば周辺の開発とデバッグが大幅に楽になることがあります。
新しく Devres - Managed Device Resource が導入されつつあります。devres はデバイスコンテキスト struct device に devres_add()(内部関数は add_dr())で確保・占有するリソースを追加し、デバイス削除時に自動的に解放する機能です。devres 機能付きクラスデバイスは追加関数に接頭辞 devm_ が付きます。一覧は Devres - Managed Device Resource の後ろの方にあります。
機能単位 | 主なヘッダやソース | 備考 |
Input, HID | linux/input.h linux/hid.h | マトリックスキーボード、電圧・電流レベル識別キーボード、シリアル接続のキーボードなどを使った構成の場合 ドキュメントは Documentation/DocBook/device-drivers/input_subsystem.html です。 input_allocate_device(), input_register_device() を使います。対の関数は input_unregister_device() または input_free_device() で、使い分けの詳細は関数のコメントにあるように input_register_device() を使った後ならば input_unregister_device() を使います。関数名はデバイス登録を思わせる名前です。実質はドライバ登録に当たります。同じドライバソースの中にキー入力を報告(伝達)する関数 input_report_key(), input_sync() が見つかると思います。代表的な PS/2 インターフェースのドライバは drivers/input/keyboard/atkbd.c です。 HID 系の(Report Descriptor, (押されているキーの一覧を通知する)Report 形式の)入力デバイスの場合 HID のドキュメントは Documentation/hid 以下にあります。全体的な説明は Documentation/hid/hid-transport.txt を参照してください。別ページに読解を進めたときの記録 HID class 調査 を書きました。デバイスの登録、ドライバの登録、デバイスとドライバの照合と突き合わせは巧妙な構造になっています。 デバイスの登録は hid_allocate_device(), hid_add_device()、対の削除は hid_destroy_device()) です。 HID Class ドライバの登録と削除は hid_register_driver(), module_hid_driver(), hid_unregister_driver() です。 |
LED | linux/leds.h | 汎用的な LED Class Device の場合 デバイスの登録は led_classdev_register()、削除は led_classdev_unregister() です。LED Class Device として登録すると LED 制御用の API が sysfs(/sys/class/leds) にノードとして作られます。CONFIG_LEDS_TRIGGERS を .config に定義しておき、struct led_classdev.default_trigger に LED トリガ名を設定しておくと、kernel 内の様々な事象に対して LED を点灯する API led_trigger_register(), led_trigger_register_simple(), led_trigger_event(), led_trigger_unregister(), led_trigger_unregister_simple() と対応づけられます。 GPIO 接続に特化した LED Class Device の場合 GPIO 接続 LED class device を参照して下さい。 |
USB (汎用) | linux/usb.h | ドキュメントは Documentation/DocBook/usb/index.html の "5. USB Core APIs" です。 デバイスの登録は usb_register_dev()、対になる削除は usb_deregister_dev() です。登録と削除関数に渡す struct usb_class_driver に struct file_operations 構造体を指すメンバ fops があります。一式の read(), write(), ioctl(), poll() API で十分に実装できるデバイスに適しています。 |
misc | linux/miscdevice.h | ドキュメントは Documentation/DocBook/kernel-api/miscdev.html です。デバイスの登録は misc_register()、対になる削除は misc_deregister() です。登録と削除関数に渡す struct miscdevice に struct file_operations 構造体を指すメンバ fops があります。「その他何でも」的なキャラクタデバイスのクラスです。ノードの Major 番号は MISC_MAJOR です。Minor 番号の一部は既に特定機能のデバイスに割り振られています。linux/miscdevice.h にある *_MINOR を参照して下さい。 一式の read(), write(), ioctl(), poll() API で十分に実装できるデバイスに適しています。Minor 番号を動的に振る場合は MISC_DYNAMIC_MINOR を struct miscdevice の minor メンバに設定して下さい。 |
Class device は他にもたくさんあります
Class device は他にも沢山の種類が存在します。cd /sys/class;ls と入力してみて出できたノード一覧がほぼ class device の一覧になります。おおよそ機能の予測がつく名前になっています。自分が作ろうとするドライバが機能として似ているならば、class device (class driver) として振る舞うように(正直に言えば、コード借りて実装負担が減るように)した方が良いでしょう。ソースコードツリーの root または drivers ディレクトリをカレントディレクトリにして、出てきたノード名を grep -r '"class-device-node-name"' * の様にして探し、実装場所を見つけてみて下さい。