i8253 PIT をアクセスするデバイスドライバ †デバイスをアクセスするドライバを作ります。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 の構成で i8253 PIT が存在する位置、右側の図が i8253 PIT とその周辺の図です。 仕様 †おおよその仕様は次の通りです。
割り込み処理は実装しません。物足りないかもしれません。 仕様に書かれた内容を見ていきます。 platform device †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 driver は platform device のためのドライバです。platform_driver_register() で kernel に登録します。platform_device_register()、platform_driver_register() それぞれの呼び出しでデバイスとドライバの組み合わせが見つかったならばドライバの probe 処理が呼ばれます。 kernel と module に組み込み場所を分割する †kernel に静的にリンクするコードと module として構成し動的にリンクするコードに分割して実装します。それぞれは次のように機能します。 kernel に静的にリンクするコード †kernel を修正するパッチと構築する方法は kernel に組み込む - i8253 PIT をアクセスするデバイスドライバ を参照して下さい。
module として構成し動的にリンクするコード †module として構築するソースは
急に大規模な開発になった様に感じるかもしれません。Linux kernel はデバイスとドライバを分けて扱っています。デバイスのためのコードとドライバのためのコードをそれぞれ書きます。 デバイスとドライバを一つのモジュール(ソースファイル)で同時に登録してはいけないの? ドライバを作ろうとするデバイスのレジスタアドレスが固定的なのにわざわざファイルを分割してまで書くか? kernel の中を見回すと platform_create_bundle() の様に kernel にデバイスとドライバを同時に登録する処理が用意されています。/drivers/block/floppy.c の様にデバイスとドライバを同一のファイル内で kernel に登録する記述も見られます。ドライバを分割して作るまでもないと判断したら、手を抜いても良いかもしれません。 sysfs node を API にする †DEVICE_ATTR() を使い sysfs node を user space から操作するための API にします。可能な操作は限定的です。単純な open-read-close, または open-write-close の流れに限定されます。一度の read, write で転送できる長さは read: PAGE_SIZE - 1 以下, write: PAGE_SIZE 以下です。i8253 のカウンタは単純な機能なのでこれで十分です。 i8253_ref_setup.c と i8253_ref.c を組み込んだことで新しく作られた sysfs ノード は /sys/devices/platform/i8253_ref.0.auto に作成されます。リンク先を参考にして下さい。 別のページ sysfs ノードからデバイスをアクセスする で PC に既に備わっている i8042 キーボード・マウス・デバイスの sysfs ノードを探っているので参考にして下さい。 汎用性が高い mknod(1)、mknod(2) で作成した major, minor 番号を持ったファイルシステム上のノードを使った API は別の機会で扱おうと考えています。 実行してみる †kernel への組み込みとドライバモジュールの構築が終了した所で実行してみます。次は実行例です。カレント・ディレクトリの移動、super user への昇格等の操作は省略してあります。 /home/furuta/work/i8253_ref # insmod i8253_ref.ko /home/furuta/work/i8253_ref # dmesg | tail -- snip -- [ 83.406414] i8253_ref: module verification failed: signature and/or required key missing - tainting kernel [ 83.406646] i8253_ref_init: Called. [ 83.406672] i8253_ref_probe: Called. pdev=0xffffffff81c2d7e0, dev=0xffffffff81c2d7f0 [ 83.406679] i8253_ref i8253_ref.0.auto: Probed. refresh_counter=0x41, control_word=0x43, channel=0x1 [ 83.406692] i8253_ref i8253_ref.0.auto: Current state. rate_default=0xffffffff, counter=0x03fd /home/furuta/work/i8253_ref # cd /sys/devices/platform/i8253_ref.0.auto /sys/devices/platform/i8253_ref.0.auto # ls -la total 0 drwxr-xr-x 3 root root 0 9月 14 13:51 . drwxr-xr-x 13 root root 0 9月 10 16:11 .. -r--r--r-- 1 root root 4096 9月 14 13:52 counter lrwxrwxrwx 1 root root 0 9月 14 13:52 driver -> ../../../bus/platform/drivers/i8253_ref -rw-r--r-- 1 root root 4096 9月 10 16:11 driver_override -r--r--r-- 1 root root 4096 9月 10 16:11 modalias drwxr-xr-x 2 root root 0 9月 10 16:11 power -rw-r--r-- 1 root root 4096 9月 14 13:52 rate lrwxrwxrwx 1 root root 0 9月 10 16:11 subsystem -> ../../../bus/platform -rw-r--r-- 1 root root 4096 9月 10 16:11 uevent /sys/devices/platform/i8253_ref.0.auto # cat rate -1 /sys/devices/platform/i8253_ref.0.auto # function counter10() > { > i=10 > while (( $i > 0 )); do cat counter | tr $"\n" " " > i=$(( $i - 1 )) > done > echo > } /sys/devices/platform/i8253_ref.0.auto # echo 1024 > rate /sys/devices/platform/i8253_ref.0.auto # cat rate 1024 /sys/devices/platform/i8253_ref.0.auto # counter10 231 130 182 138 279 244 333 347 454 478 /sys/devices/platform/i8253_ref.0.auto # echo 4096 > rate /sys/devices/platform/i8253_ref.0.auto # counter10 4077 3004 1975 1019 3989 2732 1812 48 3057 3589 /sys/devices/platform/i8253_ref.0.auto # echo 16384 > rate /sys/devices/platform/i8253_ref.0.auto # counter10 10136 5012 16161 10961 6046 814 12356 7081 2149 11508 /sys/devices/platform/i8253_ref.0.auto # echo 32768 > rate /sys/devices/platform/i8253_ref.0.auto # counter10 24422 19127 13902 8937 3789 31305 26177 20952 15912 8919 /sys/devices/platform/i8253_ref.0.auto # echo 0 > rate /sys/devices/platform/i8253_ref.0.auto # counter10 59635 52731 47698 42600 37532 32366 27360 22063 17032 11880 /sys/devices/platform/i8253_ref.0.auto # rmmod ~furuta/work/i8253_ref/i8253_ref.ko /sys/devices/platform/i8253_ref.0.auto # dmesg | tail --snip-- [ 259.205029] i8253_ref_exit: Called. [ 259.205075] i8253_ref i8253_ref.0.auto: Remove. dev=0xffffffff81c2d7f0 i8253_ref 全体のデータ構造 †次の図は i8253 PIT channel1 デバイスとこれを操作するためのドライバに関係するデータ構造です。デバイスとドライバのために最低限扱う必要がある範囲です。構造体のメンバに含まれるポインタ、内包する構造体を追えばもっと範囲は広がります。 うす青 "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 があるメンバです。 i8253_ref_setup.c: kernel に platform device を登録する †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)。詳細は kernel に静的にソースを結合する方法 を見て下さい。 initcall †次のコード片は 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 の情報です。IO ポート番号は resource を使って宣言し、i8253 デバイスの詳細な設定情報は platform_device のメンバ dev.platform_data で指した i8253_ref.h で定義した構造体 i8253_ref_platfrom_data に格納しています。 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.c がソースコード全体です。
モジュールを初期化する処理から見ていきましょう。module_init() マクロに初期化処理関数 i8253_ref_init() を指定します。これは HelloWorld モジュールと同じです。printk() で関数が呼び出されたことを表示します。デバッグ目的です。platform_driver_register() で platform driver を登録します。
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | - | | | ! - | | - | | | | ! | ! |
|
445 446 |
|
登録するドライバ i8253_ref_driver の内容を見てみましょう。platform_driver.device_driver driver.name に文字列 I8253_REF_DEVICE_NAME を指定しています。この文字列は platform_device_register で登録したデバイスの名前と照合され、一致していれば platform_driver の .probe に指定した関数 i8253_ref_probe() が呼ばれます。
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 | - | ! - | | ! - | ! - - | | | | ! | | | ! |
|
platform_driver のメンバのいくつかは関数を指すポインタです。このページに出てきた関数を指すメンバ(メソッド)を次の表に示します。
メンバ変数 | 変数が指した先の関数機能 |
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[1] = {freeze | standby | mem} に遷移する時に呼び出されます。一般的なドライバは電源供給停止、クロック停止または低周波数化などの電力管理、wakeup 準備、不意な割り込みを停止するなどを行います。 |
driver->pm.resume | state[1] = {freeze | standby | mem} から復帰する時に呼び出されます。一般的なドライバは電源供給再開、クロック供給または常用周波数に設定するなどの電力管理、wakeup 完了処理、割り込みを再開するなどを行います。 |
[1] /sys/power/state ノードです。このノードに文字列を書き込んで電力消費状態を変更します。書き込める文字列ノードを cat で読み取るとわかります。"freeze", "standby", "mem", "disk" があります。ハードウエアの対応がないか、適切なドライバがない場合は一部に限定されます。ドキュメントは /Documentation/power/states.txt です。/sys/power/state ノードに書き込む文字列と、ドライバの電力管理 dev_pm_ops call back の関係について概要を /sys/power/state にまとめてあります。
ほかにも i8253_ref driver で出てこなかった関数を指すメンバが platform_driver.device_driver driver->dev_pm_ops pm の中に多くあります。これらの中に system call の read(), write(), open(), close()(release) 機能を実装している関数を指すポインタはありません(代表的で原始的な構造体は file_operations)。user space からデバイスをアクセスするための API は別の kernel 内関数を使って登録します。user space から操作する機能を全く提供しないデバイス・ドライバも作れます。例えばハードウエア・シーケンサの代わりの様な house keeping (実行環境の維持) をするだけのドライバです。
driver->pm.suspend と suspend メンバのどちらを使えば良いの?
platform_driver と device_driver driver->dev_pm_ops pm のメンバを見てみると、それぞれに suspend メンバがあります。 driver->pm.suspend を使った方か良いと考えられます。platform_pm_suspend(), platform_pm_resume() が suspend, resume 処理で通過する関数です。実装を見てみると driver->pm.suspend が NULL でなければ suspend を呼び出す様になっています。resume も同様です。platform_driver の suspend, resume メンバが指している関数を呼ぶ処理はそれぞれ platform_legacy_suspend(), platform_legacy_resume() という名前になっています。旧式だという扱いです。Linux kernel ではこういった古い方式の処理は書き換えられ、廃止されることがあります。user space 向けの API を頑なに維持するのとは対照的です。
probe 処理 i8253_ref_probe() を見ていきます。i8253_ref_driver.probe が指している関数です。 platform_driver_register() で対応するデバイスが見つかったならば呼ばれます。kernel に組み込んだ初期化処理 i8253_ref_setup.c にて既に platform_device_register() で name=I8253_REF_DEVICE_NAME となっているデバイスは登録済みです。ですので platform_driver_register() を呼び出すと直ちに probe 関数が呼ばれます。i8253_ref_probe() の back trace を取得すると platform_driver_register() からいくつかの関数を経由して呼ばれた様に見えます。
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | - | | | | ! - | | | | | | | | | | | - | | | ! | |
|
probe 処理の順番はデバイスの挙動、確保するリソース同士の依存関係によって緻密に考える必要があります。i8253_ref ドライバではデバイス・コンテキスト i8253_ref を確保する処理 (kzalloc()) から始めました。次に resource から I/O ポート番号を取得して i8253_ref に保持する処理をします。メモリ・マップド・デバイスの場合は、物理メモリ空間を(kernel 内の)仮想アドレスへマップする処理 (例えば devm_ioremap_resource(), devm_request_mem_region(), ioremap(), ioremap_nocache() などを呼び出す処理) をする所です。
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | - | | | | | ! - | ! - | | | | | ! - | ! |
|
platform_device_register() で登録したデバイスのパラメータを platform_data メンバが指す先から取り出します。都度ポインタを辿るのが面倒なのでドライバ状態 i8253_ref のメンバ変数にコピーを保持します。rate 初期値指定がある場合 (rate_default != I8253_REF_RATE_DEFAULT_KEEP) は分周比レジスタ(ここでは rate register と呼びます)へ値を設定します。rate register を読み出すことはできないので、ドライバ状態として rate_saved に書き込んだ値を保持します。
315 316 317 318 319 320 321 322 323 324 325 326 327 | - | | | ! - | ! |
|
dev_set_drvdata() は device 構造体にドライバが確保したコンテキスト(排他状態、デバイスの状態、ステートなど)を保持する構造体を指すポインタを設定する関数です。設定したポインタを取得する dev_get_drvdata() と対になっています。
328 |
|
/drivers/base を主要な処理とする kernel の中ではデバイスを device 構造体で扱います。kernel がドライバに call back をする時に、引数あるいは間接的に渡される情報は device 構造体を指すポインタです。この中からドライバが確保したコンテキストを取り出すために dev_get_drvdata() を使います。
user space 向け API を sysfs node に作ります。DEVICE_ATTR() マクロでノードのデータ構造を作ります。DEVICE_ATTR() は _name 引数(ここでは counter と rate)に接頭辞 dev_attr_ を付けて device_attribute 型の構造体変数を宣言します。このようにいくつかのマクロは接頭辞を付けて変数を宣言するものがあります。Linux kernel ソース・コードを grep で追いにくくしています。宣言されているはずの変数が見つからない場合、接頭辞部分と思われる部分を除いて検索してみると見つかる可能性が高くなります。
S_IRUGO, S_IWUSR などはノードのパーミッションです。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 に付いては後で詳しく触れます。
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | - | | ! - | | ! - | | | ! - | ! - | ! |
|
配列 attribute*[] の要素を DEVICE_ATTR() で作ったデータ構造の attr メンバを指すポインタで構成し NULL 終端します。さらに attribute_group 構造体の attrs メンバで配列 attribute*[] を指します。多段のポインタリンクを構成する理由は、手軽な kernel 内 関数 sysfs_create_group() を使うためです。sysfs_create_group() を呼び出すと sysfs の /sys/devices/platform/i8253_ref.0.auto ディレクトリの下に counter, rate ノードが作られます。
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
| - | | | ! ! |
|
ノードを作った直後から user space のアプリケーションからアクセスされる可能性が有ることに注意して下さい。デバイスの初期化が未完了だったり、定常動作に入るまで時間が掛かったり、ドライバが起動した thread や work queue などの worker が十分に処理可能な状態で無い場合、異常な値をアプリケーションに返したり、デバイスを異常状態に遷移させる可能性が無いようにして下さい。
i8253_ref_remove() はデバイスが外された時 (platform_device_unregister()) または ドライバが外されようとした時 (platform_driver_unregister())に呼ばれます。このページの実装では platform_device_unregister() を呼び出す実装はないので、ドライバが外されようとした時の呼び出しに限定されます。
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | - | | | | ! - | | | | | | | | | - | | | ! | - | ! | | | | | | ! |
|
sysfs_remove_group() で DEVICE_ATTR(), sysfs_create_group() を使って作成したノードを削除しています。kfree() でデバイスのために確保したコンテキストを開放しています。一般的なドライバでは操作対象のデバイスが外されたか、ドライバが外されようとするので、割り込みハンドラ登録削除、デバイス停止処理(デバイスにアクセスできなくなっている場合もあるので注意)、デバイスのレジスタをアクセスするための物理アドレス-仮想アドレス割り付け解除、デバイスのために確保したメモリ領域を開放するなどの処理をします。
i8253_ref_shutdown() は shutdown または reboot するときに呼びだされます。/sys/power/state に "disk" を書き込んで hybernate する場合は poweroff call back が呼び出されます。
i8253_ref ドライバは呼びだされたことを表示するだけの処理になっています。一般的なドライバでは次のことをします。
shutdown で確保したメモリ領域などを開放するべきか?
開放せず、そのまま保持していて良いと考えています。remove が呼び出されていないので、kernel (/drivers/base) はデバイスもドライバも「存在している」という扱いになります。shutdown 以降、デバイスは機能せず read, write, その他の call back に対して -ENODEV などのエラー・コードを返すようにして、shutdown 間際で中途になる可能性がある処理を受け付けないようにするのが良いでしょう。
ドライバにとって必須の機能「デバイスをアクセスする」を見ていきます。レジスタを read, write し「デバイスの状態を取得する」、「デバイスの動作を変える」機能です。PC を構成する i8253 には 3 つのカウンタが収められています。シングルプロセッサ構成で使うことを想定した 8bit CPU 向けに作られた設計が古いデバイスです。不可分なアクセス手順があります。周期割り込み機能を持つ Channel 0 のドライバ /drivers/clocksource/i8253.c と BEEP 周波数設定機能を持つ Channel 2 のドライバ /sound/drivers/pcsp, /drivers/input/misc/pcspkr.c と他いくつかのドライバが一連のアクセス手順を不可分で実行できるように排他制御が必要です。spin lock i8253_lock をロック raw_spin_lock_irqsave()、アンロック raw_spin_unlock_irqrestore() します。
spin_lock_irqsave() と spin_unlock_irqrestore() を使うサンプルを作りたかった
raw_spin_lock_irqsave(), raw_spin_unlock_irqrestore() が使われるのは希です。他での i8253_lock の使われ方に併せて raw_*() を使いました。チェック機能が付いた spin_lock_irqsave(), spin_unlock_irqrestore() を使うのか普通です。spin_lock_irqsave() マクロの定義を見ると raw_spin_lock_irqsave() を包んでいることが分かります。
次の i8253_ref_counter_read() 関数は i8253 の counter を読み出す処理です。outb_p(), inb_p(), inb() が i8253 の IO ポートをアクセスする関数です。制御の詳細は i8253 または i8254 のデータシートを参照してください。iref->control_word ポートに制御対象の channel、アクセス手順、モードを設定します。その後、iref->refresh_counter ポートから LSB 8 ビット、MSB 8 ビットの順で値を読みます。i8253 は低速なデバイスです。ポートを読み書きする間隔を開ける必要があります。間隔が短い場合、i8253 の内部状態が十分に遷移せず、予測できない動作をします。待ち時間を入れる _p 付きの関数 outb_p(), inb_p() を使っています。
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | - | | | ! - | | | | | | | | | | | | ! |
|
outb_p() の実装はどこにあるの?
outb_p() のリンク先を辿っても、待ち処理が入った IO ポート出力処理は見つかりません。x86 と x86-64 アーキテクチャの場合はどこに実装されているのか? outb_p() の実装はどこにあるの? に探した時の記録をまとめてあります。Linux kernel ソース・コードを追っていくときの参考にもなるでしょう。
次の i8253_ref_rate_write() 関数は i8253 の rate (分周比)に書き込む処理です。手順は counter を読み出す処理とほぼ同様です。inb_p(), inb() 関数に代えて outb_p(), outb() を使用しています。
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | - | | | ! - | | | | | | | | | | | | | | ! |
|
sysfs_create_group() で登録した counter, rate ノードの getter, setter 関数の実装を見ていきます。DEVICE_ATTR() マクロの第 3 引数が getter 関数です。慣習的に _show という接尾辞を付けます。第 4 引数が setter 関数です。慣習的に _store という接尾辞を付けます。
176
177
178
179
180
181
| - | | ! |
|
i8253_ref_counter_show() は read() system call に対して返す文字列を構築する関数です。文字列の格納先は buf 引数が指す先になります。NUL 終端を含まない文字列の最大長は PAGE_SIZE - 1 です。NUL 終端込みで PAGE_SIZE です。
DEVICE_ATTR() の _show() 関数(メソッド)で返せる文字列の長さを PAGE_SIZE で決めているのは何処?
DEVICE_ATTR() の _show() 関数が返せる文字列の長さを決めているのは dev_attr_show() です。_show() 関数が返した値をチェックしています。本当に buf が指している領域に NUL 終端込みで PAGE_SIZE バイトの領域は確保されているのか? file system に近い側の関数をたどると sysfs_kf_seq_show() で呼び出す前に領域を memset で PAGE_SIZE バイト分 0x00 で fill しています。さらに file system 近い側 sequential read を実装している部分 seq_read() で seq_buf_alloc() を呼び出し PAGE_SIZE バイト確保しています。seq_read() 内では条件分岐で traverse() を呼び出す経路もあります。こちらに実行が進むのは open() 直後に lseek() する場合のはずです。利点のない使い方です。
kernel ソースの読みを深く進めると kernfs_seq_next() が常に NULL を返すことから、open() 直後の read() を終わらせた時点で EOF に達し、buf が指した領域に継ぎ足すように user space に返す文字列を伸ばしたり、領域の伸長が行われないことも確認できます。
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | - | | | | | | | ! - | | - | | ! | ! |
|
dev 引数は device 構造体を指します。そこから dev_get_drvdata() でドライバが確保した i8253_ref 構造体を指すポインタを取り出し、返そうとする文字列を構築するための情報を取り出します。改行を含めるかどうかは任意です。1 行で済ませるのか複数行で構成するかも任意です。cat コマンドで読んだ場合の利便性を考慮すると文字列の最後は改行にしたほうが良いでしょう。
i8253_ref_rate_show() もほぼ同様な実装です。read back する値を保持していないとき (iref->rate_saved == I8253_REF_RATE_DEFAULT_KEEP) 例外的な文字列を返す処理が付加されています。例外的な状況を返す場合の取り決めは user space のアプリケーションを作る人と相談して決めるのが良いでしょう。システム・コール・エラーで返すのが良いのか、特別な意味を持つ文字列で返すのか。都合の良い方法を選択してください。
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | - | | | | | | | ! - | | - | | ! - | | ! | ! |
|
i8253_ref_rate_store() は write() に対応するコードです。buf が指した先に書き込んだ文字列、count はその長さになります。user space の Application が文字列を NUL で終端しなくても、NUL で終端されます。NUL を含めない文字列の最大長は PAGE_SIZE です。
DEVICE_ATTR() の _store() 関数に渡される文字列長の最大は?そして NUL 終端されているの?
kernfs_fop_write() で文字列の最大長が制限されています。NUL 終端を含めない文字列の長さは PAGE_SIZE です。NUL 終端は必ず付きます。最も長い文字列が書き込まれたならば、buf[0]..buf[PAGE_SIZE-1] に文字が書き込まれ buf[PAGE_SIZE]=NUL となります。
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | - | | | | | | | | ! - | | | | - | | ! | | - | | ! - | | ! - | | ! | | | ! |
|
文字列から整数に変換する関数は /include/linux/kernel.h で定義されています。libc に有る関数と似たような名前の関数が用意されています。simple_strtoul(), kstrtoul(), sscanf() などがあります。user space から渡されたパラメータは十分に定義域のチェック、受け取り可能な長さの制限を施して下さい。パラメータに複雑な文法があり、それをパースする場合はどんなパターンであっても無限ループに入らず、関数が return する様にして下さい。
simple_strtoul() は obsolete ですか...
"This function is obsolete. Please use kstrtoul instead."
ドライバの電源管理 call back のうち、suspend と resume を実装してあります。suspend と resume は /sys/power/state に "freeze", "standby", "mem" のいずれかを書き込むと呼び出されます。プラットホームによっては 3 つのうち一部だけが有効な書き込みになっています。主記憶の内容を swap 領域に書きだして停止する "disk" の書き込を含めた電源管理の調査内容は /sys/power/state を参照して下さい。
i8253_ref_suspend() はデバイスを低消費電力状態に遷移させる必要があるときに呼び出されます。i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | - | | | | ! - | | | - | | | ! | - | | ! | | | | | ! |
|
一般的なドライバで suspend 処理で実装される内容は次の通りです。
機器全体の機能として CPU が停止しても、周辺デバイスを動作させ、ユーザーに対して何らかの働きかけを続ける、あるいは機器外部との関係を続けるような仕様の場合はそれに準じた設定をします。
i8253 でも僅かな低消費電力化は可能かもしれない
PC-AT 互換機の回路構成で使われている i8253 channel 1 はカウンタを止める mode5 があります。mode5 に設定すれば counter FF の状態遷移が止まるので僅かに低消費電力化が可能かもしれません。
i8253_ref_resume() はデバイスを低消費電力から復帰させる必要があるときに呼び出されます。resume の実装同様に i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | - | | | | ! - | | | - | | | ! | - | | ! | | | | ! |
|
i8253_ref_exit() は i8253_ref.ko モジュールが kernel から rmmod されるときに呼び出されます。関数に付いている __exit 修飾子はドライバを kernel に static link する時は、kernel image に含めない様に指定する修飾です。
435 436 437 438 439 440 441 442 443 | - | | ! - | | ! |
|
platform_driver_unregister() を呼び出しドライバの登録を解除します。platform_driver_unregister() を呼び出すと remove call back i8253_ref_remove() が呼び出されます。remove 関数の中でデバイスを機能停止し、probe() で確保した領域、割り込み設定(i8253_ref ドライバでは割り込みは実装されていません)、デバイスアクセス用のノードなどを解放し、kernel から取得した資源を返します。モジュール全体で確保した資源があるならば、ドライバ登録を解除した後の方が都合が良いでしょう。i8253_ref.ko モジュールはモジュール全体で確保した資源が無いので特に何もしていません。
次はこのページに出てきた主な項目です。並びはページの構成と変えて整理してあります。