#author("2017-10-17T08:23:41+09:00","default:afuruta","afuruta")
#author("2017-10-17T08:26:09+09:00","default:afuruta","afuruta")
* i8253 PIT をアクセスするデバイスドライバ [#j256b81d]
デバイスをアクセスするドライバを作ります。Windows そして古くは MS-DOS を実行できる PC ならば備わっていると考えられる i8253 Programmable Interval Timer (PIT) にアクセスします。今時の PC ならばほぼ使われていないはずの Channel 1 (D-RAM Refresh conter あるいは D-RAM Refresh timer) のレジスタにアクセスします。

#textbox(note, 今時の 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:https://en.wikipedia.org/wiki/Intel_8253]], [[i8237:https://en.wikipedia.org/wiki/Intel_8237]],
 [[440BX North Bridge:https://www.google.co.jp/search?num=100&newwindow=1&site=&source=hp&q=82443BX+Host+Bridge%2FController+datasheet&oq=82443BX+Host+Bridge%2FController+datasheet]] 
}}
左側の図が PC の構成で i8253 PIT が存在する位置、右側の図が i8253 PIT とその周辺の図です。
次に示す左側の図が PC の構成で i8253 PIT が存在する位置、右側の図が i8253 PIT とその周辺の図です。
CENTER:&ref(bus_tree_to_i8253.png); &ref(i8253.png);
* 仕様 [#r1d54f9f]
おおよその仕様は次の通りです。
|項目|仕様|h
|動作環境|PC-AT 仕様のパソコン、PentiumIII あるいはこれより新しいプロセッサを使用している|
|対象デバイス|i8253 PIT channel1|
|デバイスの形態|platform device|
|ドライバの機能|read counter, write rate, readback rate (ドライバ内部に保存した値を読み出し)|
|ドライバの種類|platform device driver|
|ドライバ実装形態|kernel に直接組み込む部分とモジュール部分に分割|
|user space API|sysfs node|
割り込み処理は実装しません。物足りないかもしれません。

仕様に書かれた内容を見ていきます。
** platform device [#hb677a0e]
platform device とは実行環境に固定的に接続され、ほかのデバイスの初期化をしなくても使えるデバイスです。多くの場合、プロセッサから出ているバスに直接接続されているか、初期化が不要または boot loader などで初期化が済んでしまっている BUS bridge を通して接続されているデバイスです。&ogdefs(platform_device_register(),platform_device_register); で kernel に登録します。

#textbox(note, on board の PCI 接続デバイスはどの様な扱いになるの?){{
&ogfileone(/drivers/pci,/drivers/pci); にある PCI bus ドライバ群で扱います。PCI bus bridge の初期化が必要なため PCI device として扱います。PCI bus のアドレス空間配置などを Linux kernel で都合よく扱えるように初期化し管理しています(&ogdefs(pci_assign_resource(),pci_assign_resource);、&ogdefs(pci_reassign_resource(),pci_reassign_resource);)。PCI デバイスを発見して登録する処理 &ogdefs(pci_device_add(),pci_device_add);、PCI デバイスのためのドライバを登録する処理 &ogdefs(pci_register_driver(),pci_register_driver); 辺りを手掛かりにコードを追いかけてみてください。 
}}

** platform driver [#hb677a0e]
platform driver は platform device のためのドライバです。&ogdefs(platform_driver_register(),platform_driver_register); で kernel に登録します。&ogdefs(platform_device_register(),platform_device_register);、&ogdefs(platform_driver_register(),platform_driver_register); それぞれの呼び出しでデバイスとドライバの組み合わせが見つかったならばドライバの probe 処理が呼ばれます。

** kernel と module に組み込み場所を分割する [#y3bfccd1]
kernel に静的にリンクするコードと module として構成し動的にリンクするコードに分割して実装します。それぞれは次のように機能します。

*** kernel に静的にリンクするコード [#c83dc2c2]
kernel を修正するパッチと構築する方法は [[kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ]] を参照して下さい。

|実装|機能|h
|kernel に静的にリンクするコード [[i8253_ref_setup.c]]|i8253 channel 1 を platform device として登録します。&br;&ogfileone(/arch/x86/kernel,arch/x86/kernel); に配置|
|kernel の [[Makefile>i8253_ref-kernel-Makefile]]|[[i8253_ref_setup.c]] を kernel に静的に結合します。 &br;&ogfileone(/arch/x86/kernel/Makefile,arch/x86/kernel/Makefile); を修正|
|kernel の [[Kconfig>i8253_ref-kernel-kconfig]]|[[i8253_ref_setup.c]] を組み込むかどうか &span(ConsoleIn){make menuconfig}; で選択する。*_defconfig チェックします。&br;&ogfileone(/arch/x86/Kconfig,arch/x86/Kconfig); を修正|
|device header file [[i8253_control.h]] |i8253 のレジスタを定義します。&br;&ogfileone(include/linux,include/linux); に配置|
|driver header file  [[i8253_ref.h]]|platform device の IO 空間配置と初期設定をドライバに渡す定義です。&br;&ogfileone(include/linux,include/linux); に配置|

*** module として構成し動的にリンクするコード [#g1946788]
module として構築するソースは &ref(i8253_ref.tar.gz); からダウンロードして下さい。前節の「kernel に静的にリンクするコード」を kernel に組み込んで再起動した後に構築を行って下さい。構築と kernel への組み込みは [[HelloWorld ページの Makefile>HelloWorld#x4106800]] を参考に行って下さい。
|実装|機能|h
|module として構成したドライバ [[i8253_ref.c]]|i8253 channel 1 をアクセスします。|
|上の [[Makefile>i8253_ref-module-Makefile]]|[[i8253_ref.c]] を kernel object として構築します。|
急に大規模な開発になった様に感じるかもしれません。Linux kernel はデバイスとドライバを分けて扱っています。デバイスのためのコードとドライバのためのコードをそれぞれ書きます。
#textbox(thought, デバイスとドライバを一つのモジュール(ソースファイル)で同時に登録してはいけないの?){{
ドライバを作ろうとするデバイスのレジスタアドレスが固定的なのにわざわざファイルを分割してまで書くか? kernel の中を見回すと &ogdefs(platform_create_bundle(),platform_create_bundle); の様に kernel にデバイスとドライバを同時に登録する処理が用意されています。&ogfileone(/drivers/block/floppy.c); の様にデバイスとドライバを同一のファイル内で kernel に登録する記述も見られます。ドライバを分割して作るまでもないと判断したら、手を抜いても良いかもしれません。
}}
** sysfs node を API にする [#fc3f7e95]
&ogdefs(DEVICE_ATTR(),DEVICE_ATTR,device.h); を使い sysfs node を user space から操作するための API にします。可能な操作は限定的です。単純な open-read-close, または open-write-close の流れに限定されます。一度の read, write で転送できる長さは read: &ogdefs(PAGE_SIZE,PAGE_SIZE); - 1 以下, write: &ogdefs(PAGE_SIZE,PAGE_SIZE); 以下です。i8253 のカウンタは単純な機能なのでこれで十分です。

i8253_ref_setup.c と i8253_ref.c を組み込んだことで新しく作られた sysfs ノード は [[/sys/devices/platform/i8253_ref.0.auto>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#q18ecde1]] に作成されます。[[リンク先>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#q18ecde1]]を参考にして下さい。


別のページ [[sysfs ノードからデバイスをアクセスする]] で PC に既に備わっている i8042 キーボード・マウス・デバイスの sysfs ノードを探っているので参考にして下さい。

汎用性が高い [[mknod(1):https://linuxjm.osdn.jp/html/GNU_fileutils/man1/mknod.1.html]]、[[mknod(2):https://linuxjm.osdn.jp/html/LDP_man-pages/man2/mknod.2.html]] で作成した major, minor 番号を持ったファイルシステム上のノードを使った API は別の機会で扱おうと考えています。

* 実行してみる [#u764cf56]
kernel への組み込みとドライバモジュールの構築が終了した所で実行してみます。次は実行例です。カレント・ディレクトリの移動、super user への昇格等の操作は省略してあります。
 
#pre(soft){{
&span(ConsoleOut){/home/furuta/work/i8253_ref # };&span(ConsoleIn){insmod i8253_ref.ko};
&span(ConsoleOut){/home/furuta/work/i8253_ref # };&span(ConsoleIn){dmesg | tail};
-- snip --
&span(ConsoleOut){[   83.406414] i8253_ref: module verification failed: signature and/or required key missing - tainting kernel};
&span(ConsoleOut){[   83.406646] i8253_ref_init: Called.};
&span(ConsoleOut){[   83.406672] i8253_ref_probe: Called. pdev=0xffffffff81c2d7e0, dev=0xffffffff81c2d7f0};
&span(ConsoleOut){[   83.406679] i8253_ref i8253_ref.0.auto: Probed. refresh_counter=0x41, control_word=0x43, channel=0x1};
&span(ConsoleOut){[   83.406692] i8253_ref i8253_ref.0.auto: Current state. rate_default=0xffffffff, counter=0x03fd};
&span(ConsoleOut){/home/furuta/work/i8253_ref # };&span(ConsoleIn){cd /sys/devices/platform/i8253_ref.0.auto};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){ls -la};
&span(ConsoleOut){total 0};
&span(ConsoleOut){drwxr-xr-x  3 root root    0  9月 14 13:51 .};
&span(ConsoleOut){drwxr-xr-x 13 root root    0  9月 10 16:11 ..};
&span(ConsoleOut){-r--r--r--  1 root root 4096  9月 14 13:52 counter};
&span(ConsoleOut){lrwxrwxrwx  1 root root    0  9月 14 13:52 driver -> ../../../bus/platform/drivers/i8253_ref};
&span(ConsoleOut){-rw-r--r--  1 root root 4096  9月 10 16:11 driver_override};
&span(ConsoleOut){-r--r--r--  1 root root 4096  9月 10 16:11 modalias};
&span(ConsoleOut){drwxr-xr-x  2 root root    0  9月 10 16:11 power};
&span(ConsoleOut){-rw-r--r--  1 root root 4096  9月 14 13:52 rate};
&span(ConsoleOut){lrwxrwxrwx  1 root root    0  9月 10 16:11 subsystem -> ../../../bus/platform};
&span(ConsoleOut){-rw-r--r--  1 root root 4096  9月 10 16:11 uevent};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){cat rate};
&span(ConsoleOut){-1};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){function counter10()};
&span(ConsoleOut){> {};
&span(ConsoleOut){> i=10};
&span(ConsoleOut){> while (( $i > 0 )); do cat counter | tr $"\n" " "};
&span(ConsoleOut){> i=$(( $i - 1 ))};
&span(ConsoleOut){> done};
&span(ConsoleOut){> echo};
&span(ConsoleOut){> }};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){echo 1024 > rate};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){cat rate};
&span(ConsoleOut){1024};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){counter10};
&span(ConsoleOut){231 130 182 138 279 244 333 347 454 478 };
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){echo 4096 > rate};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){counter10};
&span(ConsoleOut){4077 3004 1975 1019 3989 2732 1812 48 3057 3589 };
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){echo 16384 > rate};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){counter10};
&span(ConsoleOut){10136 5012 16161 10961 6046 814 12356 7081 2149 11508 };
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){echo 32768 > rate};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){counter10};
&span(ConsoleOut){24422 19127 13902 8937 3789 31305 26177 20952 15912 8919 };
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){echo 0 > rate};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){counter10};
&span(ConsoleOut){59635 52731 47698 42600 37532 32366 27360 22063 17032 11880 };
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){rmmod ~furuta/work/i8253_ref/i8253_ref.ko};
&span(ConsoleOut){/sys/devices/platform/i8253_ref.0.auto # };&span(ConsoleIn){dmesg | tail};
--snip--
&span(ConsoleOut){[  259.205029] i8253_ref_exit: Called.};
&span(ConsoleOut){[  259.205075] i8253_ref i8253_ref.0.auto: Remove. dev=0xffffffff81c2d7f0};
}}

* i8253_ref 全体のデータ構造 [#wc5356f6]

次の図は i8253 PIT channel1 デバイスとこれを操作するためのドライバに関係するデータ構造です。デバイスとドライバのために最低限扱う必要がある範囲です。構造体のメンバに含まれるポインタ、内包する構造体を追えばもっと範囲は広がります。
CENTER:&ref(i8253_ref_data_structure.png);

うす青 &ref(i8253_ref_light_blue_color.png); の構造体は Linux kernel で定義された構造体です。うす橙 &ref(i8253_ref_light_orange_color.png); の構造体はこのページに書かれた i8253 ドライバのために定義した構造体です。

"Prepared in i8253_ref_setup.c" と書かれた囲みの中が kernel の初期化処理に追加した静的なデータ構造です。ドライバを作る立場から見て platform device の仕様・特性を保持する構造体になります。

"Allocated in i8253_ref.c" と書かれた囲みの中がドライバの状態維持のため i8253_ref.c で動的確保するデータ構造です。ドライバ自身の状態、デバイスの状態のコピーなどを保持します。より多くの機能と処理を行うドライバは排他制御・同期・参照カウンタなどをメンバーとして持ちます。

#textbox(note, device 構造体の driver_data メンバ getter/setter){{
&ogdefs(device,device,device.h); 構造体の driver_data メンバは getter (&ogdefs(dev_get_drvdata(),dev_get_drvdata);) と setter (&ogdefs(dev_set_drvdata(),dev_set_drvdata);) を使ってアクセスします。珍しく排他制御などの付加的な機能がない getter/setter があるメンバです。
}}

* i8253_ref_setup.c: kernel に platform device を登録する [#gb51e87e]

kernel の初期化処理で i8253 を platform device として登録します。&ogfileone(/arch/x86/kernel); の下に [[i8253_ref_setup.c]] を新しく作成し、この中に実装します。
#textbox(note, ディレクトリの下に配置したソースをコンパイル対象にする){{
C 言語で書いたソースをディレクトリに置いただけではコンパイルされません。少なくともそのディレクトリの Makefile を編集してコンパイル対象に加える様にします (&ogfileone(/Documentation/kbuild/makefiles.txt);)。Makefile の位置は一部例外的に上位ディレクトリに有るかもしれません。多くの場合、Kconfig ファイルを編集して &span(ConsoleIn){make menuconfig}; または *_defconfig ファイルの指定にてコンパイル対象にするか選択できる様にします (&ogfileone(/Documentation/kbuild/kconfig-language.txt);)。詳細は [[kernel に静的にソースを結合する方法>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#patch_in_detail]] を見て下さい。
}}
** initcall [#q22e6b93]
次のコード片は [[i8253_ref_setup.c]] の初期化処理部分です。
#code(c,overflow:scroll,/i8253_ref_device_initcall/..=-3+15,i8253_ref_setup.c);
kernel 初期化処理の時に呼ぶ関数を &ogdefs(arch_initcall(),arch_initcall); で指定します。呼び出す順番を気にする場合は loader script (.lds) で厳密に制御する、既に存在する他の初期化処理コードを修正するなどして都合の良さそうな前後順になる様にして下さい。

&ogdefs(arch_initcall(),arch_initcall); で指定した i8253_ref_device_initcall() の処理は &ogdefs(platform_device_register(),platform_device_register); で i8253 デバイスを登録するだけです。エラーチェックをしています。しかし、エラーは発生しないはずです。ここでエラーになる場合は &ogdefs(resource, resource, ioport.h); が衝突しているか、メモリが kernel 起動時から少ない場合です。

** platform device と platform driver の繋がり [#z225cc3f]
次のコード片は i8253_ref_device_initcall() で kernel に登録した i8253 device の情報です。IO ポート番号は &ogdefs(resource, resource, ioport.h); を使って宣言し、i8253 デバイスの詳細な設定情報は &ogdefs(platform_device, platform_device,platform_device.h); のメンバ dev.platform_data で指した [[i8253_ref.h]] で定義した構造体 i8253_ref_platfrom_data に格納しています。 kernel のデータ構造 &ogdefs(resource, resource, ioport.h);, &ogdefs(platform_device, platform_device,platform_device.h); とドライバで定義したマクロとデータ構造 [[i8253_ref.h]] を参考に読んで見て下さい。
#code(c,overflow:scroll,/counter and control port resource/../Register i8253 PIT platform device/,i8253_ref_setup.c);
kernel に登録した情報の中に device と driver を結びつけるための情報が含まれています。&span(Code){I8253_REF_DEVICE_NAME}; です。この文字列をキーにして platform device と platform driver を対応づけます。&ogdefs(resource, resource, ioport.h); 構造体の配列にも文字列 &span(Code){I8253_REF_RESOURCE_REFRESH_COUNTER};, &span(Code){I8253_REF_RESOURCE_CONTROL_WORD}; が含まれています。これは &span(ConsoleOut){$ };&span(ConsoleIn){cat /proc/ioports}; で表示される様になります。文字列をキーにして driver 側で I/O ポート番号を &ogdefs(platform_get_resource_byname(),platform_get_resource_byname);で取得できます。デバイス固有のパラメータは &ogdefs(platform_device,platform_device,platform_device.h);.&ogdefs(dev.platform_data,device,linux/device.h); で指した先に構造体 i8253_ref_platfrom_data を配置し、driver に渡します。 
#code(c,overflow:scroll,/I8253_REF_DEVICE_NAME/../I8253_REF_RESOURCE_CONTROL_WORD/,i8253_ref.h);

* i8253_ref.c: i8253 をアクセスするドライバ [#gae3e986]

[[i8253_ref.c]] がソースコード全体です。
** init [#a78be01f]
モジュールを初期化する処理から見ていきましょう。&ogdefs(module_init(),module_init,init.h); マクロに初期化処理関数 i8253_ref_init() を指定します。これは [[HelloWorld]] モジュールと同じです。&ogdefs(printk(),printk,printk.h); で関数が呼び出されたことを表示します。デバッグ目的です。&ogdefs(platform_driver_register(),platform_driver_register); で platform driver を登録します。
#code(c,overflow:scroll,/i8253_ref_init/../^}$/,begin-=5,i8253_ref.c);
#code(c,overflow:scroll,/module_init/..+1,i8253_ref.c);
登録するドライバ i8253_ref_driver の内容を見てみましょう。&ogdefs(platform_driver,platform_driver,platform_device.h);.&ogdefs(device_driver driver,device_driver,device.h);.name に文字列 I8253_REF_DEVICE_NAME を指定しています。この文字列は &ogdefs(platform_device_register); で登録したデバイスの名前と照合され、一致していれば &ogdefs(platform_driver,platform_driver,platform_device.h); の .probe に指定した関数 i8253_ref_probe() が呼ばれます。
#code(c,overflow:scroll,/dev_pm_ops/../i8253_ref_shutdown/, begin-=4, end+=1,i8253_ref.c);
*** platform_driver のメソッド(関数テーブル) [#s6d57faf]
&ogdefs(platform_driver,platform_driver,platform_device.h); のメンバのいくつかは関数を指すポインタです。このページに出てきた関数を指すメンバ(メソッド)を次の表に示します。
|メンバ変数|変数が指した先の関数機能|h
|probe|対応するデバイスが見つかった(kernel に{登録されていたら | 登録されたら})呼び出されます。一般的なドライバはレジスタやデバイスのメモリをマップ(bus 直結のデバイス)、デバイスを存在確認、ドライバ状態領域確保、thread または work queue などの worker context 初期化・起動、割り込みハンドラ設置、初期化などをします。|
|remove|対応するデバイスが外された(kernel から削除された)、または、ドライバが削除されたら呼び出されます。一般的なドライバはデバイスを停止する(外された場合はアクセスできないので注意が必要)、デバイスからの割り込みを止めてハンドラを外す、ドライバか起動した thread, work queue などの実行コンテキストを停止・終了する、デバイス・コンテキストを解放する、メモリマップ解除(bus 直結のデバイス)を行い probe 前の状態にします。|
|shutdown|shutdown または reboot の時に呼び出されます。一般的なドライバはデバイスを停止する、デバイスからの割り込みを止めてハンドラを外す、thread, work queue などの実行コンテキストで継続中の処理を早期に終了する。電池動作機器の場合、デバイスに供給されている電源を遮断、クロックを停止などの追加的な停止処理を行う場合もあります。|
|driver->pm.suspend|state&sup([1]); = {freeze | standby | mem} に遷移する時に呼び出されます。一般的なドライバは電源供給停止、クロック停止または低周波数化などの電力管理、wakeup 準備、不意な割り込みを停止するなどを行います。|
|driver->pm.resume|state&sup([1]); = {freeze | standby | mem} から復帰する時に呼び出されます。一般的なドライバは電源供給再開、クロック供給または常用周波数に設定するなどの電力管理、wakeup 完了処理、割り込みを再開するなどを行います。|
&sup([1]); [[/sys/power/state]] ノードです。このノードに文字列を書き込んで電力消費状態を変更します。書き込める文字列ノードを cat で読み取るとわかります。"freeze", "standby", "mem", "disk" があります。ハードウエアの対応がないか、適切なドライバがない場合は一部に限定されます。ドキュメントは &ogfileone(/Documentation/power/states.txt); です。[[/sys/power/state]] ノードに書き込む文字列と、ドライバの電力管理 &ogdefs(dev_pm_ops); call back の関係について概要を [[/sys/power/state]] にまとめてあります。

ほかにも i8253_ref driver で出てこなかった関数を指すメンバが &ogdefs(platform_driver,platform_driver,platform_device.h);.&ogdefs(device_driver driver,device_driver,device.h);->&ogdefs(dev_pm_ops pm,dev_pm_ops,pm.h); の中に多くあります。これらの中に system call の read(), write(), open(), close()(release) 機能を実装している関数を指すポインタはありません(代表的で原始的な構造体は &ogdefs(file_operations);)。user space からデバイスをアクセスするための API は別の kernel 内関数を使って登録します。user space から操作する機能を全く提供しないデバイス・ドライバも作れます。例えばハードウエア・シーケンサの代わりの様な house keeping (実行環境の維持) をするだけのドライバです。
#textbox(note,driver->pm.suspend と suspend メンバのどちらを使えば良いの?){{
&ogdefs(platform_driver,platform_driver,platform_device.h); と &ogdefs(device_driver driver,device_driver,device.h);->&ogdefs(dev_pm_ops pm,dev_pm_ops,pm.h); のメンバを見てみると、それぞれに suspend メンバがあります。 driver->pm.suspend を使った方か良いと考えられます。&ogdefs(platform_pm_suspend(),platform_pm_suspend);, &ogdefs(platform_pm_resume(),platform_pm_resume); が suspend, resume 処理で通過する関数です。実装を見てみると driver->pm.suspend が NULL でなければ suspend を呼び出す様になっています。resume も同様です。&ogdefs(platform_driver,platform_driver,platform_device.h); の suspend, resume メンバが指している関数を呼ぶ処理はそれぞれ &ogdefs(platform_legacy_suspend(),platform_legacy_suspend);, &ogdefs(platform_legacy_resume(),platform_legacy_resume); という名前になっています。旧式だという扱いです。Linux kernel ではこういった古い方式の処理は書き換えられ、廃止されることがあります。user space 向けの API を頑なに維持するのとは対照的です。
}}

** probe [#m174041c]

probe 処理 i8253_ref_probe() を見ていきます。i8253_ref_driver.probe が指している関数です。 &ogdefs(platform_driver_register(),platform_driver_register); で対応するデバイスが見つかったならば呼ばれます。kernel に組み込んだ初期化処理 i8253_ref_setup.c にて既に &ogdefs(platform_device_register(),platform_device_register); で name=I8253_REF_DEVICE_NAME となっているデバイスは登録済みです。ですので &ogdefs(platform_driver_register(),platform_driver_register); を呼び出すと直ちに probe 関数が呼ばれます。i8253_ref_probe() の back trace を取得すると &ogdefs(platform_driver_register(),platform_driver_register); からいくつかの関数を経由して呼ばれた様に見えます。
#code(c,overflow:scroll,/i8253_ref_probe/../platform_get_resource_byname/,begin-=6,i8253_ref.c);
probe 処理の順番はデバイスの挙動、確保するリソース同士の依存関係によって緻密に考える必要があります。i8253_ref ドライバではデバイス・コンテキスト i8253_ref を確保する処理 (&ogdefs(kzalloc());) から始めました。次に &ogdefs(resource,resource,ioport.h); から I/O ポート番号を取得して i8253_ref に保持する処理をします。メモリ・マップド・デバイスの場合は、物理メモリ空間を(kernel 内の)仮想アドレスへマップする処理 (例えば &ogdefs(devm_ioremap_resource(),devm_ioremap_resource);, &ogdefs(devm_request_mem_region(),devm_request_mem_region);, &ogdefs(ioremap(),ioremap);, &ogdefs(ioremap_nocache(),ioremap_nocache); などを呼び出す処理) をする所です。

#code(c,overflow:scroll,/platform_get_resource_byname.*I8253_REF_RESOURCE_REFRESH_COUNTER/../control_word/,i8253_ref.c);
&ogdefs(platform_device_register(),platform_device_register); で登録したデバイスのパラメータを platform_data メンバが指す先から取り出します。都度ポインタを辿るのが面倒なのでドライバ状態 i8253_ref のメンバ変数にコピーを保持します。rate 初期値指定がある場合 (rate_default != I8253_REF_RATE_DEFAULT_KEEP) は分周比レジスタ(ここでは rate register と呼びます)へ値を設定します。rate register を読み出すことはできないので、ドライバ状態として rate_saved に書き込んだ値を保持します。
#code(c,overflow:scroll,/dev[[:space:]]*=[[:space:]]*&\(pdev[-]>dev/../rate_saved/,menu,i8253_ref.c);

&ogdefs(dev_set_drvdata()); は &ogdefs(device,device,/include/linux/device.h); 構造体にドライバが確保したコンテキスト(排他状態、デバイスの状態、ステートなど)を保持する構造体を指すポインタを設定する関数です。設定したポインタを取得する &ogdefs(dev_get_drvdata()); と対になっています。

#code(c,overflow:scroll,/dev_set_drvdata/../dev_set_drvdata/,menu,i8253_ref.c);

&ogfile(/drivers/base); を主要な処理とする kernel の中ではデバイスを &ogdefs(device,device,/include/linux/device.h); 構造体で扱います。kernel がドライバに call back をする時に、引数あるいは間接的に渡される情報は &ogdefs(device,device,/include/linux/device.h); 構造体を指すポインタです。この中からドライバが確保したコンテキストを取り出すために &ogdefs(dev_get_drvdata()); を使います。

user space 向け API を sysfs node に作ります。&ogdefs(DEVICE_ATTR(),DEVICE_ATTR,device.h); マクロでノードのデータ構造を作ります。&ogdefs(DEVICE_ATTR(),DEVICE_ATTR,device.h); は _name 引数(ここでは counter と rate)に接頭辞  dev_attr_ を付けて &ogdefs(device_attribute,device_attribute,device.h); 型の構造体変数を宣言します。このようにいくつかのマクロは接頭辞を付けて変数を宣言するものがあります。Linux kernel ソース・コードを grep で追いにくくしています。宣言されているはずの変数が見つからない場合、接頭辞部分と思われる部分を除いて検索してみると見つかる可能性が高くなります。

&ogdefs(S_IRUGO,S_IRUGO, stat.h);, &ogdefs(S_IWUSR,S_IWUSR,stat.h); などはノードのパーミッションです。system call の chmod() と同様です。i8253_ref_counter_show, i8253_ref_rate_show, i8253_ref_rate_store は *_show が read() system call に対応する関数、*_store() が write() system call に対応する関数です。_show, _store に付いては後で詳しく触れます。

#code(c,overflow:scroll,/DEVICE_ATTR.*counter/../[.]attrs\s*=\s*i8253_ref_attrs/,begin-=4,end+=1,i8253_ref.c);

配列 &ogdefs(attribute*,attribute,sysfs.h);[] の要素を &ogdefs(DEVICE_ATTR(),DEVICE_ATTR,device.h); で作ったデータ構造の attr メンバを指すポインタで構成し NULL 終端します。さらに &ogdefs(attribute_group, attribute_group,sysfs.h); 構造体の attrs メンバで配列 &ogdefs(attribute*,attribute);[] を指します。多段のポインタリンクを構成する理由は、手軽な kernel 内 関数 &ogdefs(sysfs_create_group(),sysfs_create_group); を使うためです。&ogdefs(sysfs_create_group(),sysfs_create_group); を呼び出すと sysfs の /sys/devices/platform/i8253_ref.0.auto ディレクトリの下に counter, rate ノードが作られます。

#code(c,overflow:scroll,/sysfs_create_group/../^}$/,i8253_ref.c);

ノードを作った直後から user space のアプリケーションからアクセスされる可能性が有ることに注意して下さい。デバイスの初期化が未完了だったり、定常動作に入るまで時間が掛かったり、ドライバが起動した thread や work queue などの worker が十分に処理可能な状態で無い場合、異常な値をアプリケーションに返したり、デバイスを異常状態に遷移させる可能性が無いようにして下さい。
** remove [#o2f070f6]
i8253_ref_remove() はデバイスが外された時 (&ogdefs(platform_device_unregister());) または ドライバが外されようとした時 (&ogdefs(platform_driver_unregister());)に呼ばれます。このページの実装では &ogdefs(platform_device_unregister()); を呼び出す実装はないので、ドライバが外されようとした時の呼び出しに限定されます。
#code(c,overflow:scroll,/i8253_ref_remove/../^}$/,begin-=6,i8253_ref.c);
&ogdefs(sysfs_remove_group()); で &ogdefs(DEVICE_ATTR(),DEVICE_ATTR,device.h);, &ogdefs(sysfs_create_group()); を使って作成したノードを削除しています。&ogdefs(kfree()); でデバイスのために確保したコンテキストを開放しています。一般的なドライバでは操作対象のデバイスが外されたか、ドライバが外されようとするので、割り込みハンドラ登録削除、デバイス停止処理(デバイスにアクセスできなくなっている場合もあるので注意)、デバイスのレジスタをアクセスするための物理アドレス-仮想アドレス割り付け解除、デバイスのために確保したメモリ領域を開放するなどの処理をします。

** shutdown [#p749d1f7]

i8253_ref_shutdown() は shutdown または reboot するときに呼びだされます。[[/sys/power/state]] に "disk" を書き込んで hybernate する場合は poweroff call back が呼び出されます。
#code(c,overflow:scroll,/i8253_ref_shutdown/../^}$/,begin-=4,i8253_ref.c);
i8253_ref ドライバは呼びだされたことを表示するだけの処理になっています。一般的なドライバでは次のことをします。
- ドライバの therad や workqueue などで継続している処理をキャンセルして早期に終了させるか、最大で 100ms 程度で終了する見込みがあるのなら処理完了まで待つ。
- デバイスの動作を停止する。DMA を使用して転送処理中であれば、キャンセルを試みる。
- 割り込みを禁止または発生しないようにして、デバイスが電源 off 間際に予期しない動作を始めても、kernel の shutdown, reboot 処理を妨げないようにする。
#textbox(note,shutdown で確保したメモリ領域などを開放するべきか?){{
開放せず、そのまま保持していて良いと考えています。remove が呼び出されていないので、kernel (&ogfileone(/drivers/base);) はデバイスもドライバも「存在している」という扱いになります。shutdown 以降、デバイスは機能せず read, write, その他の call back に対して -&ogdefs(ENODEV); などのエラー・コードを返すようにして、shutdown 間際で中途になる可能性がある処理を受け付けないようにするのが良いでしょう。
}}

** read and write [#y950da90]

ドライバにとって必須の機能「デバイスをアクセスする」を見ていきます。レジスタを read, write し「デバイスの状態を取得する」、「デバイスの動作を変える」機能です。PC を構成する i8253 には 3 つのカウンタが収められています。シングルプロセッサ構成で使うことを想定した 8bit CPU 向けに作られた設計が古いデバイスです。不可分なアクセス手順があります。周期割り込み機能を持つ Channel 0 のドライバ &ogfileone(/drivers/clocksource/i8253.c); と BEEP 周波数設定機能を持つ Channel 2 のドライバ &ogfileone(/sound/drivers/pcsp);, &ogfileone(/drivers/input/misc/pcspkr.c); と他いくつかのドライバが一連のアクセス手順を不可分で実行できるように排他制御が必要です。spin lock &ogdefs(i8253_lock,i8253_lock); をロック &ogdefs(raw_spin_lock_irqsave(),raw_spin_lock_irqsave);、アンロック &ogdefs(raw_spin_unlock_irqrestore(),raw_spin_unlock_irqrestore); します。
#textbox(thought,spin_lock_irqsave() と spin_unlock_irqrestore() を使うサンプルを作りたかった){{
&ogdefs(raw_spin_lock_irqsave());, &ogdefs(raw_spin_unlock_irqrestore()); が使われるのは希です。他での &ogdefs(i8253_lock); の使われ方に併せて raw_*() を使いました。チェック機能が付いた
 &ogdefs(spin_lock_irqsave());, &ogdefs(spin_unlock_irqrestore()); を使うのか普通です。&ogdefs(spin_lock_irqsave()); マクロの定義を見ると &ogdefs(raw_spin_lock_irqsave()); を包んでいることが分かります。 
}}

*** read counter [#tc85339c]

次の i8253_ref_counter_read() 関数は i8253 の counter を読み出す処理です。&ogdefs(outb_p());, &ogdefs(inb_p());, &ogdefs(inb()); が i8253 の IO ポートをアクセスする関数です。制御の詳細は i8253 または i8254 のデータシートを参照してください。&span(Code){iref->control_word}; ポートに制御対象の channel、アクセス手順、モードを設定します。その後、&span(Code){iref->refresh_counter}; ポートから LSB 8 ビット、MSB 8 ビットの順で値を読みます。i8253 は低速なデバイスです。ポートを読み書きする間隔を開ける必要があります。間隔が短い場合、i8253 の内部状態が十分に遷移せず、予測できない動作をします。待ち時間を入れる _p 付きの関数 &ogdefs(outb_p());, &ogdefs(inb_p()); を使っています。
#code(c,overflow:scroll,/i8253_ref_counter_read/../^}$/,begin-=5,i8253_ref.c);
#textbox(note,outb_p() の実装はどこにあるの?){{
&ogdefs(outb_p()); のリンク先を辿っても、待ち処理が入った IO ポート出力処理は見つかりません。x86 と x86-64 アーキテクチャの場合はどこに実装されているのか? [[outb_p() の実装はどこにあるの?]] に探した時の記録をまとめてあります。Linux kernel ソース・コードを追っていくときの参考にもなるでしょう。
}}

*** write rate [#e7ceadca]

次の i8253_ref_rate_write() 関数は i8253 の rate (分周比)に書き込む処理です。手順は counter を読み出す処理とほぼ同様です。&ogdefs(inb_p());, &ogdefs(inb()); 関数に代えて &ogdefs(outb_p());, &ogdefs(outb()); を使用しています。
#code(c,overflow:scroll,/i8253_ref_rate_write/../^}$/,begin-=5,i8253_ref.c);
** show and store [#qd77e49d]
&ogdefs(sysfs_create_group()); で登録した counter, rate ノードの getter, setter 関数の実装を見ていきます。&ogdefs(DEVICE_ATTR()); マクロの第 3 引数が getter 関数です。慣習的に _show という接尾辞を付けます。第 4 引数が setter 関数です。慣習的に _store という接尾辞を付けます。
#code(c,/DEVICE_ATTR.*counter/../DEVICE_ATTR.*rate/,begin-=4,i8253_ref.c);

*** _show() - read() system call に対応する処理 [#dfab3eee]

i8253_ref_counter_show() は read() system call に対して返す文字列を構築する関数です。文字列の格納先は buf 引数が指す先になります。NUL 終端を含まない文字列の最大長は &ogdefs(PAGE_SIZE); - 1 です。NUL 終端込みで &ogdefs(PAGE_SIZE); です。
#textbox(note,DEVICE_ATTR() の _show() 関数(メソッド)で返せる文字列の長さを PAGE_SIZE で決めているのは何処?){{
&ogdefs(DEVICE_ATTR()); の _show() 関数が返せる文字列の長さを決めているのは &ogdefs(dev_attr_show()); です。_show() 関数が返した値をチェックしています。本当に buf が指している領域に NUL 終端込みで &ogdefs(PAGE_SIZE); バイトの領域は確保されているのか? file system に近い側の関数をたどると &ogdefs(sysfs_kf_seq_show()); で呼び出す前に領域を memset で &ogdefs(PAGE_SIZE); バイト分 0x00 で fill しています。さらに file system 近い側 sequential read を実装している部分 &ogdefs(seq_read()); で  &ogdefs(seq_buf_alloc()); を呼び出し &ogdefs(PAGE_SIZE); バイト確保しています。&ogdefs(seq_read()); 内では条件分岐で &ogdefs(traverse(),traverse,seq_file.c); を呼び出す経路もあります。こちらに実行が進むのは open() 直後に lseek() する場合のはずです。利点のない使い方です。&br;kernel ソースの読みを深く進めると &ogdefs(kernfs_seq_next()); が常に NULL を返すことから、open() 直後の read() を終わらせた時点で EOF に達し、buf が指した領域に継ぎ足すように user space に返す文字列を伸ばしたり、領域の伸長が行われないことも確認できます。
}}
#code(c,overflow:scroll,/i8253_ref_counter_show/../^}$/,begin-=9,i8253_ref.c);
dev 引数は &ogdefs(device,device,/include/linux/device.h); 構造体を指します。そこから &ogdefs(dev_get_drvdata()); でドライバが確保した i8253_ref 構造体を指すポインタを取り出し、返そうとする文字列を構築するための情報を取り出します。改行を含めるかどうかは任意です。1 行で済ませるのか複数行で構成するかも任意です。&span(ConsoleIn){cat}; コマンドで読んだ場合の利便性を考慮すると文字列の最後は改行にしたほうが良いでしょう。

i8253_ref_rate_show() もほぼ同様な実装です。read back する値を保持していないとき (&span(code){iref->rate_saved == I8253_REF_RATE_DEFAULT_KEEP};) 例外的な文字列を返す処理が付加されています。例外的な状況を返す場合の取り決めは user space のアプリケーションを作る人と相談して決めるのが良いでしょう。システム・コール・エラーで返すのが良いのか、特別な意味を持つ文字列で返すのか。都合の良い方法を選択してください。
#code(c,overflow:scroll,/i8253_ref_rate_show/../^}$/,begin-=9,i8253_ref.c);

*** _store() - write() system call に対応する処理 [#ibd50f03]

i8253_ref_rate_store() は write() に対応するコードです。buf が指した先に書き込んだ文字列、count はその長さになります。user space の Application が文字列を NUL で終端しなくても、NUL で終端されます。NUL を含めない文字列の最大長は  &ogdefs(PAGE_SIZE); です。
#textbox(note,DEVICE_ATTR() の _store() 関数に渡される文字列長の最大は?そして NUL 終端されているの?){{
&ogdefs(kernfs_fop_write()); で文字列の最大長が制限されています。NUL 終端を含めない文字列の長さは &ogdefs(PAGE_SIZE); です。NUL 終端は必ず付きます。最も長い文字列が書き込まれたならば、buf[0]..buf[PAGE_SIZE-1] に文字が書き込まれ buf[PAGE_SIZE]=NUL となります。
}}
#code(c,overflow:scroll,/i8253_ref_rate_store/../^}$/,begin-=10,i8253_ref.c);
文字列から整数に変換する関数は &ogfileone(/include/linux/kernel.h); で定義されています。libc に有る関数と似たような名前の関数が用意されています。&ogdefs(simple_strtoul());, &ogdefs(kstrtoul());, &ogdefs(sscanf()); などがあります。user space から渡されたパラメータは十分に定義域のチェック、受け取り可能な長さの制限を施して下さい。パラメータに複雑な文法があり、それをパースする場合はどんなパターンであっても無限ループに入らず、関数が return する様にして下さい。
#textbox(thought, simple_strtoul() は obsolete ですか...){{
"This function is obsolete. Please use kstrtoul instead."

}}

** suspend and resume [#j9be16c0]

ドライバの電源管理 call back のうち、suspend と resume を実装してあります。suspend と resume は [[/sys/power/state]] に "freeze", "standby", "mem" のいずれかを書き込むと呼び出されます。プラットホームによっては 3 つのうち一部だけが有効な書き込みになっています。主記憶の内容を swap 領域に書きだして停止する "disk" の書き込を含めた電源管理の調査内容は [[/sys/power/state]] を参照して下さい。

*** suspend [#x712e06d]

i8253_ref_suspend() はデバイスを低消費電力状態に遷移させる必要があるときに呼び出されます。i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。
#code(c,overflow:scroll,/i8253_ref_suspend/../^}$/,begin-=6,i8253_ref.c);
一般的なドライバで suspend 処理で実装される内容は次の通りです。
- 一連の制御シーケンスを伴うデバイス制御をしている場合は、それが完了するまで待つか処理をキャンセルする。
- 割り込みを禁止するか、デバイスが wakeup 機能を持っているならば割り込みを wakeup 信号として機能するように設定する。割り込みが発生した後、 thread や workqueue などを使って割り込み応答処理を「始めようとしている」、あるいは「始まっている」場合の動作を注意深く実装して下さい。
- デバイスを低消費電力状態に遷移させる: クロック周波数を低くするあるいは停止する、電力消費が大きい回路ブロックを停止する。

#textbox(note){{
機器全体の機能として CPU が停止しても、周辺デバイスを動作させ、ユーザーに対して何らかの働きかけを続ける、あるいは機器外部との関係を続けるような仕様の場合はそれに準じた設定をします。
}}
#textbox(thought, i8253 でも僅かな低消費電力化は可能かもしれない){{
PC-AT 互換機の回路構成で使われている i8253 channel 1 はカウンタを止める mode5 があります。mode5 に設定すれば counter FF の状態遷移が止まるので僅かに低消費電力化が可能かもしれません。
}}
*** resume [#h51fc36d]
i8253_ref_resume() はデバイスを低消費電力から復帰させる必要があるときに呼び出されます。resume の実装同様に i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。
#code(c,overflow:scroll,/i8253_ref_resume/../^}$/,begin-=6,i8253_ref.c);
** exit [#cc84e856]
i8253_ref_exit() は i8253_ref.ko モジュールが kernel から rmmod されるときに呼び出されます。関数に付いている &ogdefs(__exit,__exit,/include/linux/init.h); 修飾子はドライバを kernel に static link する時は、kernel image に含めない様に指定する修飾です。
#code(c,overflow:scroll,/i8253_ref_exit/../^}$/,begin-=4,i8253_ref.c);
&ogdefs(platform_driver_unregister()); を呼び出しドライバの登録を解除します。&ogdefs(platform_driver_unregister()); を呼び出すと  remove call back i8253_ref_remove() が呼び出されます。remove 関数の中でデバイスを機能停止し、probe() で確保した領域、割り込み設定(i8253_ref ドライバでは割り込みは実装されていません)、デバイスアクセス用のノードなどを解放し、kernel から取得した資源を返します。モジュール全体で確保した資源があるならば、ドライバ登録を解除した後の方が都合が良いでしょう。i8253_ref.ko モジュールはモジュール全体で確保した資源が無いので特に何もしていません。
* まとめ [#e375f7d9]
次はこのページに出てきた主な項目です。並びはページの構成と変えて整理してあります。
- [[kernel にソースを組み込む>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ]]
-- [[Makefile>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#q2da03c6]]
-- [[Kconfig>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#m2a019a5]]
- &ogdefs(platform_device);
-- &ogdefs(platform_device_register());
-- &ogdefs(platform_device_unregister());
-- &ogdefs(resource,resource,ioport.h);
- struct &ogdefs(device,device,device.h);
-- platform_data
-- driver_data
--- &ogdefs(dev_set_drvdata());
--- &ogdefs(dev_get_drvdata());
- &ogdefs(platform_driver);
-- &ogdefs(platform_driver_register());
-- &ogdefs(platform_driver_unregister());
-- [[platform device との対応>#z225cc3f]]
- driver module
-- [[init>#z225cc3f]]
-- [[exit>#cc84e856]]
- driver call back
-- [[probe>#m174041c]]
-- [[remove>#o2f070f6]]
-- [[shutdown>#p749d1f7]]
-- &ogdefs(dev_pm_ops);
--- [[suspend>#x712e06d]]
--- [[resume>#h51fc36d]]
--- [[sys/power/state]]
- &ogdefs(DEVICE_ATTR());
--  [[/sys/devices/platform/i8253_ref.0.auto>kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ#q18ecde1]]
-- &ogdefs(sysfs_create_group());
-- &ogdefs(sysfs_remove_group());
-- [[_show()>#dfab3eee]]
-- [[_store()>#ibd50f03]]
- &ogdefs(kzalloc());, &ogdefs(kfree());
- [[spin lock>spin lock による排他制御]]
- &ogdefs(outb());, &ogdefs(inb());, &ogdefs(outb_p());, &ogdefs(inb_p());
-- [[outb_p() の実装はどこにあるの?]]

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS