デバイスをアクセスするドライバを作ります。Windows そして古くは MS-DOS を実行できる PC ならば備わっていると考えられる i8253 Programmable Interval Timer (PIT) にアクセスします。今時の PC ならばほぼ使われていないはずの Channel 1 (D-RAM Refresh conter あるいは D-RAM Refresh timer) のレジスタにアクセスします。
今時の PC の i8253 Channel1 は D-RAM 制御に使われていないの?
完全な PC の回路が公開されていないので、断言はできません。30pin SIMM, 72pin DIMM を使用するのであれば、i8253-i8237 (PIT-DMAC) の組あるいは i8253(i8254) を timer として容易に Refresh 回路を構成できます。これより機能が増えた DIMM を使用する回路になると i8253 を使用する利点が無くなります。North bridge または CPU に統合された D-RAM controller であれは i8253 を D-RAM controller の一部に組み込むのを止めていると考えられます。i8253, i8237, 440BX North Bridge
おおよその仕様は次の通りです。
項目 | 仕様 |
動作環境 | PC-AT 仕様のパソコン、PentiumIII あるいはこれより新しいプロセッサを使用している |
対象デバイス | i8253 PIT channel1 |
デバイスの形態 | platform device |
ドライバの機能 | read counter, write rate, readback rate (ドライバ内部に保存した値を読み出し) |
ドライバの種類 | platform device driver |
ドライバ実装形態 | Kernel に直接組み込む部分とモジュール部分に分割 |
Userland API | sysfs node |
割り込み処理は実装しません。物足りないかもしれません。
仕様に書かれた内容を見ていきます。
platform device とは実行環境に固定的に接続され、ほかのデバイスの初期化をしなくても使えるデバイスです。多くの場合、プロセッサから出ているバスに直接接続されているか、初期化が不要または boot loader などで初期化が済んでしまっている BUS bridge を通して接続されているデバイスです。platform_device_register() で kernel に登録します。
on board の PCI 接続デバイスはどの様な扱いになるの?
/drivers/pci にある PCI bus ドライバ群で扱います。PCI bus bridge の初期化が必要なため PCI device として扱います。PCI bus のアドレス空間配置などを Linux kernel で都合よく扱えるように初期化し管理しています(pci_assign_resource()、pci_reassign_resource())。PCI デバイスを発見して登録する処理 pci_device_add()、PCI デバイスのためのドライバを登録する処理 pci_register_driver() 辺りを手掛かりにコードを追いかけてみてください。
platform driver は platform device のためのドライバです。platform_driver_register() で kernel に登録します。platform_device_register()、platform_driver_register() それぞれの呼び出しでデバイスとドライバの組み合わせが見つかったならばドライバの probe 処理が呼ばれます。
kernel に静的にリンクするコードと module として構成し動的にリンクするコードに分割して実装します。それぞれは次のように機能します。
実装 | 機能 |
kernel に静的にリンクするコード i8253_ref_setup.c | i8253 channel 1 を platform device として登録します。 /arch/x86/kernel に配置 |
kernel の Makefile | i8253_ref_setup.c を kernel に静的に結合します。 /arch/x86/kernel/Makefile を修正 |
kernel の Kconfig | i8253_ref_setup.c を組み込むかどうか make menuconfig で選択する。*_defconfig チェックします。 /arch/x86/Kconfig を修正 |
device header file i8253_control.h | i8253 のレジスタを定義します。 include/linux に配置 |
driver header file i8253_ref.h | platform device の IO 空間配置と初期設定をドライバに渡す定義です。 include/linux に配置 |
module として構成したドライバ i8253_ref.c | i8253 channel 1 をアクセスします。 |
上の Makefile | i8253_ref.c を kernel object として構築します。 |
急に大規模な開発になった様に感じるかもしれません。Linux Kernel はデバイスとドライバを分けて扱っています。デバイスのためのコードとドライバのためのコードをそれぞれ書きます。
デバイスとドライバを一つのモジュール(ソースファイル)で同時に登録してはいけないの?
ドライバを作ろうとするデバイスのレジスタアドレスが固定的なのにわざわざファイルを分割してまで書くか? kernel の中を見回すと platform_create_bundle() の様に kernel にデバイスとドライバを同時に登録する処理が用意されています。/drivers/block/floppy.c の様にデバイスとドライバを同一のファイル内で kernel に登録する記述も見られます。ドライバを分割して作るまでもないと判断したら、手を抜いても良いかもしれません。
DEVICE_ATTR() を使い sysfs node を Userland から操作するための API にします。可能な操作は限定的です。単純な open-read-close, または open-write-close の流れに限定されます。一度の read, write で転送できる長さは PAGE_SIZE 以下です。i8253 のカウンタは単純な機能なのでこれで十分です。
汎用性が高い mknod(1)、mknod(2) で作成した major, minor 番号を持ったファイルシステム上のノードを使った API は別の機会で扱おうと考えています。
Linux kernel のデバイスとドライバの管理作法に従ってデバイスを kernel に登録すれば sysfs 上に対応するノードができます。sysfs は多くの linux で /sys に mount されています。 sysfs のツリーを辿ってデバイスを探してみましょう。
platform device は /sys/devices/platform の下にノードが並びます。デバイス名の一部または機能名の一部がディレクトリ名になって並んでいます。環境によりデバイスの有無は仮想的な物も含めて違うので、存在するディレクトリは増減します。shell command を操作して /sys/devices 以下のノードと /sys/devices/platform 以下のノードの一覧を出してみます。
~ $ cd /sys/devices /sys/devices $ ls -la total 0 drwxr-xr-x 16 root root 0 Jul 24 01:43 . dr-xr-xr-x 13 root root 0 Jul 24 01:43 .. drwxr-xr-x 10 root root 0 Jul 24 01:43 LNXSYSTM:00 drwxr-xr-x 3 root root 0 Jul 24 01:43 breakpoint drwxr-xr-x 5 root root 0 Jul 24 01:43 cpu drwxr-xr-x 5 root root 0 Jul 24 01:43 cstate_core drwxr-xr-x 5 root root 0 Jul 24 01:43 cstate_pkg drwxr-xr-x 3 root root 0 Jul 24 01:43 intel_bts drwxr-xr-x 5 root root 0 Jul 24 01:43 msr drwxr-xr-x 16 root root 0 Jul 24 01:43 pci0000:00 drwxr-xr-x 22 root root 0 Jul 24 01:43 platform drwxr-xr-x 10 root root 0 Jul 24 01:43 pnp0 drwxr-xr-x 3 root root 0 Jul 24 01:43 software drwxr-xr-x 9 root root 0 Jul 24 01:43 system drwxr-xr-x 3 root root 0 Jul 24 01:43 tracepoint drwxr-xr-x 18 root root 0 Jul 24 01:43 virtual /sys/devices/platform $ cd platform /sys/devices/platform $ ls -la total 0 drwxr-xr-x 22 root root 0 Jul 24 01:43 . drwxr-xr-x 16 root root 0 Jul 24 01:43 .. drwxr-xr-x 3 root root 0 Jul 24 01:45 ACPI000C:00 drwxr-xr-x 4 root root 0 Jul 24 01:45 Fixed MDIO bus.0 drwxr-xr-x 3 root root 0 Jul 24 01:45 INT33FF:00 drwxr-xr-x 3 root root 0 Jul 24 01:45 INT33FF:01 drwxr-xr-x 3 root root 0 Jul 24 01:45 INT33FF:02 drwxr-xr-x 3 root root 0 Jul 24 01:45 INT33FF:03 drwxr-xr-x 3 root root 0 Jul 24 01:45 PNP0103:00 drwxr-xr-x 3 root root 0 Jul 24 01:45 PNP0C0C:00 drwxr-xr-x 3 root root 0 Jul 24 01:45 PNP0C0E:00 drwxr-xr-x 3 root root 0 Jul 24 01:45 alarmtimer drwxr-xr-x 4 root root 0 Jul 20 11:14 coretemp.0 drwxr-xr-x 3 root root 0 Jul 24 01:45 efi-framebuffer.0 drwxr-xr-x 5 root root 0 Jul 24 01:45 i8042 drwxr-xr-x 3 root root 0 Jul 24 01:45 microcode drwxr-xr-x 3 root root 0 Jul 24 01:45 pcspkr drwxr-xr-x 2 root root 0 Jul 24 01:45 power drwxr-xr-x 4 root root 0 Jul 24 01:45 reg-dummy drwxr-xr-x 4 root root 0 Jul 24 01:45 serial8250 drwxr-xr-x 3 root root 0 Jul 24 01:45 snd-soc-dummy -rw-r--r-- 1 root root 4096 Jul 24 01:45 uevent drwxr-xr-x 3 root root 0 Jul 24 01:45 vboxdrv.0
i8042 ディレクトリを見てみましょう。このディレクトリには PS/2 keyboard と mouse デバイスが対応します。/sys/devices/platform/i8042/serio0 ディレクトリの下にある bind_mode (kernel の中で対応している関数は serio_set_bind_mode(), serio_show_bind_mode()), description (serio_show_description(), description ノードに文字列 "i8042 KBD port" を設定しているのは i8042_create_kbd_port()) ノードを読み出してみます。デバイスを制御、機能確認するためノードです。このようなノードの多くは shell から cat, echo command で読み出し、書き込みできる様に実装されています。手軽にスクリプトで動作確認・制御できる Userland API になっています。
/sys/devices/platform $ cd i8042 /sys/devices/platform/i8042 $ ls -la total 0 drwxr-xr-x 5 root root 0 Jul 24 01:45 . drwxr-xr-x 22 root root 0 Jul 24 01:43 .. lrwxrwxrwx 1 root root 0 Jul 24 01:53 driver -> ../../../bus/platform/drivers/i8042 -rw-r--r-- 1 root root 4096 Jul 24 01:53 driver_override -r--r--r-- 1 root root 4096 Jul 24 01:53 modalias drwxr-xr-x 2 root root 0 Jul 24 01:53 power drwxr-xr-x 4 root root 0 Jul 24 01:53 serio0 drwxr-xr-x 4 root root 0 Jul 24 01:53 serio1 lrwxrwxrwx 1 root root 0 Jul 24 01:53 subsystem -> ../../../bus/platform -rw-r--r-- 1 root root 4096 Jul 24 01:53 uevent /sys/devices/platform/i8042 $ cd serio0 /sys/devices/platform/i8042/serio0$ ls -la total 0 drwxr-xr-x 4 root root 0 Jul 24 01:53 . drwxr-xr-x 5 root root 0 Jul 24 01:45 .. -rw-r--r-- 1 root root 4096 Jul 24 01:54 bind_mode -r--r--r-- 1 root root 4096 Jul 24 01:54 description --w------- 1 root root 4096 Jul 24 01:54 drvctl -r--r--r-- 1 root root 4096 Jul 24 01:54 firmware_id drwxr-xr-x 2 root root 0 Jul 24 01:54 id -r--r--r-- 1 root root 4096 Jul 24 01:54 modalias drwxr-xr-x 2 root root 0 Jul 24 01:54 power lrwxrwxrwx 1 root root 0 Jul 24 01:54 subsystem -> ../../../../bus/serio -rw-r--r-- 1 root root 4096 Jul 24 01:54 uevent /sys/devices/platform/i8042/serio0 $ cat bind_mode auto /sys/devices/platform/i8042/serio0 $ cat description i8042 KBD port
あちこちにある uevent node はどんな機能があるの?
書き込む (uevent_store()) と uevent を発行する (kobject_uevent_env()) 機能と、読み込む (uevent_show()) 機能が実装されています。読み込む機能は一部のノード、例えば /sys/class/input/* の下にある uevent ノードに実装 (input_dev_uevent()) されています。読み込む機能は uevent で発行される情報の一部をいつでも取得できる様になっています。
uevent は Userland の udev 機能が使っている socket 通信 API (AF_NETLINK.NETLINK_KOBJECT_UEVENT) に kernel から userland に向かう送信をし、最近のディストリビューションでは使われなくなりつつある /proc/sys/kernel/hotplug または /sys/kernel/uevent_helper に設定された uevent helper (uevent_helper) 実行ファイルを実行する (call_usermodehelper_setup(), call_usermodehelper_exec()) 機能です。実行ファイルは慣例的に /sbin/hotplug (CONFIG_UEVENT_HELPER_PATH) です。
次の図は i8253 PIT channel1 デバイスとこれを操作するためのドライバに関係するデータ構造です。デバイスとドライバのために最低限扱う必要がある範囲です。構造体のメンバに含まれるポインタ、内包する構造体を追えばもっと範囲は広がります。
うす青 の構造体は Linux Kernel で定義された構造体です。うす橙
の構造体はこのページに書かれた i8253 ドライバのために定義した構造体です。
"Prepared in i8253_ref_setup.c" と書かれた囲みの中が kernel の初期化処理に追加した静的なデータ構造です。ドライバを作る立場から見て platform device の仕様・特性を保持する構造体になります。
"Allocated in i8253_ref.c" と書かれた囲みの中がドライバの状態維持のため i8253_ref.c で動的確保するデータ構造です。ドライバ自身の状態、デバイスの状態のコピーなどを保持します。より多くの機能と処理を行うドライバは排他制御・同期・参照カウンタなどをメンバーとして持ちます。
device 構造体の driver_data メンバ getter/setter
device 構造体の driver_data メンバは getter (dev_get_drvdata()) と setter (dev_set_drvdata()) を使ってアクセスします。珍しく排他制御などの付加的な機能がない getter/setter があるメンバです。
kernel の初期化処理で i8253 を platform device として登録します。/arch/x86/kernel の下に i8253_ref_setup.c を新しく作成し、この中に実装します。
ディレクトリの下に配置したソースをコンパイル対象にする
C 言語で書いたソースをディレクトリに置いただけではコンパイルされません。少なくともそのディレクトリの Makefile を編集してコンパイル対象に加える様にします (/Documentation/kbuild/makefiles.txt)。Makefile の位置は一部例外的に上位ディレクトリに有るかもしれません。多くの場合、Kconfig ファイルを編集して make menuconfig または *_defconfig ファイルの指定にてコンパイル対象にするか選択できる様にします (/Documentation/kbuild/kconfig-language.txt)。この章の後半で詳しく触れます。
次のコード片は i8253_ref_setup.c の初期化処理部分です。
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | - | ! - | | | - | | | | ! | ! |
|
kernel 初期化処理の時に呼ぶ関数を arch_initcall() で指定します。呼び出す順番を気にする場合は loader script (.lds) で厳密に制御する、既に存在する他の初期化処理コードを修正するなどして都合の良さそうな前後順になる様にして下さい。
arch_initcall() で指定した i8253_ref_device_initcall() の処理は platform_device_register() で i8253 デバイスを登録するだけです。エラーチェックをしています。しかし、エラーは発生しないはずです。ここでエラーになる場合は resource が衝突しているか、メモリが kernel 起動時から少ない場合です。
次のコード片は i8253_ref_device_initcall() で kernel に登録した i8253 device の情報です。Kernel のデータ構造 resource, platform_device とドライバで定義したマクロとデータ構造 i8253_ref.h を参考に読んで見て下さい。
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | - | ! - - | | | ! - | | | ! ! - | ! - | | ! - | | ! - | | | | | - | ! ! - | |
|
kernel に登録した情報の中に device と driver を結びつけるための情報が含まれています。I8253_REF_DEVICE_NAME です。この文字列をキーにして platform device と platform driver を対応づけます。resource 構造体の配列にも文字列 I8253_REF_RESOURCE_REFRESH_COUNTER, I8253_REF_RESOURCE_CONTROL_WORD が含まれています。これは $ cat /proc/ioports で表示される様になります。文字列をキーにして driver 側で I/O ポート番号を platform_get_resource_byname()で取得できます。デバイス固有のパラメータは platform_device.dev.platform_data で指した先に構造体 i8253_ref_platfrom_data を配置し、driver に渡します。
25 26 27 28 29 |
|
i8253_ref_setup.c を kernel に静的に結合する(スタティック・リンク)する方法を見ていきます。新しく作ったソース・ファイルを kernel にスタティック・リンクするのに最低限必要なことは、ソースを配置したディレクトリまたはその上流に辿る経路で近い場所にある Makefile を修正しコンパイル・リンクする対象に加えることです。加えてソースファイルから上流に辿る経路で近い場所にある Kconfig ファイルに新しく作ったソースをコンパイル・リンク対象にするのかどうか make menuconfig で選択できる様にします。
make menuconfig で新しく加えたソース・ファイルをコンパイル対象にするかどうか選択できる様にしておくと、チーム開発をしている状況での問題の切り分け、開発環境の足並みが揃っていない状況で柔軟に対応することができます。
/arch/x86/kernel/Makefile の変更箇所は次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
obj-$(CONFIG_I8253_REFRESH) += i8253_ref_setup.o は CONFIG_I8253_REFRESH を展開した結果によって obj- += i8253_ref_setup.o または obj-y += i8253_ref_setup.o と解釈されます。ここでは出てきませんが CONFIG_I8253_REFRESH が tristate (モジュール) の場合は obj-m += i8253_ref_setup.o となる場合が有ります。obj-y += i8253_ref_setup.o となった場合、kernel に静的に結合します。
obj-* += file.o | 構築結果 |
obj- += file.o | file.c はコンパイルされない |
obj-y += file.o | file.c は kernel に静的に結合(スタティックリンクされる) |
obj-m += file.o | file.c はモジュールとして構築され file.ko が作られる (このページでは扱いません) |
arch/x86/Kconfig の変更箇所は次の通りです。この Kconfig の位置は例外的に Makefile と ソースファイル i8253_ref_setup.c が有るディレクトリと違っています。この修正で make menuconfig で i8253_ref_setup.c を kernel に組み込むかどうか前節の Makefile の修正と併せ選択することができる様になります。詳細な書き方は /Documentation/kbuild/kconfig-language.txt を参照して下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
config I8253_REFRESH で選択項目を宣言します。ここでは接頭辞 CONFIG_ を付けずに書くことに注意して下さい。bool は y か n (定義しない)の選択だと言う意味です。default は n (定義しない)、 depends on X86 は X86(CONFIG_X86) が定義されれば選択項目に現れることを意味します。help は make menuconfig で help を要求したときに表示されるテキストです。他の項目で ---help--- と書かれているところもあります。どちらも同様に解釈されます。help の部分はインデントに意味があります。他の部分のインデントも慣例的に似せて書くことをお勧めします。
追加した config 項目は make menuconfig のどこに現れるの?
make menuconfig を実行して追加した項目がどこに行ったか分からなくなるかもしれません。手っ取り早く知るには make menuconfig で対話的設定を開始したところで / を押してマクロ名を検索します。このページの例では I8253_REFRESH を探す文字列とします。Location: 以下に場所のパスが示されます。丁寧に Kconfig の階層構造を探すより早いです。
他の architecture や platform の場合、どこにソース・コードを追加して、どこの Makefile や Kconfig の修正をしたら良いの?
開発ターゲットの基板に実装されたテバイスを追加するために /arch/processor 以下に配置されたファイルを修正したり、追加するやり方に迷うかもしれません。ディレクトリ構成はプロセッサ(CPU)毎に大きく違います。見回してみて、しっくりする修正方法を考えてください。似たようなデバイスを見つけて並べるように修正するのが良いでしょう。他のプロセッサに比べて派生品種が多い ARM 系では /arch/arm/plat-platform に SoC 毎に派生するコード、/arch/arm/mach-machine に基板品種毎に派生する(付加した周辺回路によって派生する)コードが含まれることが多いです(SoC 毎に派生するコードもあります)。