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 とその周辺の図です。

bus_tree_to_i8253.png i8253.png

仕様

おおよその仕様は次の通りです。

項目仕様
動作環境PC-AT 仕様のパソコン、PentiumIII あるいはこれより新しいプロセッサを使用している
対象デバイスi8253 PIT channel1
デバイスの形態platform device
ドライバの機能read counter, write rate, readback rate (ドライバ内部に保存した値を読み出し)
ドライバの種類platform device driver
ドライバ実装形態kernel に直接組み込む部分とモジュール部分に分割
user space APIsysfs node

割り込み処理は実装しません。物足りないかもしれません。

仕様に書かれた内容を見ていきます。

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 をアクセスするデバイスドライバ を参照して下さい。

実装機能
kernel に静的にリンクするコード i8253_ref_setup.ci8253 channel 1 を platform device として登録します。
/arch/x86/kernel に配置
kernel の Makefilei8253_ref_setup.c を kernel に静的に結合します。
/arch/x86/kernel/Makefile を修正
kernel の Kconfigi8253_ref_setup.c を組み込むかどうか make menuconfig で選択する。*_defconfig チェックします。
/arch/x86/Kconfig を修正
device header file i8253_control.hi8253 のレジスタを定義します。
include/linux に配置
driver header file i8253_ref.hplatform device の IO 空間配置と初期設定をドライバに渡す定義です。
include/linux に配置

module として構成し動的にリンクするコード

module として構築するソースは filei8253_ref.tar.gz からダウンロードして下さい。前節の「kernel に静的にリンクするコード」を kernel に組み込んで再起動した後に構築を行って下さい。構築と kernel への組み込みは HelloWorld ページの Makefile を参考に行って下さい。

実装機能
module として構成したドライバ i8253_ref.ci8253 channel 1 をアクセスします。
上の Makefilei8253_ref.c を kernel object として構築します。

急に大規模な開発になった様に感じるかもしれません。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 デバイスとこれを操作するためのドライバに関係するデータ構造です。デバイスとドライバのために最低限扱う必要がある範囲です。構造体のメンバに含まれるポインタ、内包する構造体を追えばもっと範囲は広がります。

i8253_ref_data_structure.png

うす青 i8253_ref_light_blue_color.png の構造体は Linux kernel で定義された構造体です。うす橙 i8253_ref_light_orange_color.png の構造体はこのページに書かれた 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 があるメンバです。

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 の初期化処理部分です。

filei8253_ref_setup.c
Expand allFold all
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
-
|
!
 
-
|
|
|
-
|
|
|
|
!
|
!
 
 
 
/*
 * Register i8253 PIT platform device.
 */
static int __init i8253_ref_device_initcall(void)
{       int             ret;
 
        /* Register i8253 as platform device. */
        ret = platform_device_register(&i8253_ref_platform_device);
        if (ret != 0) {
                pr_err("%s: Can not register platform %s device. "
                       "ret=%d\n",
                        __func__, I8253_REF_DEVICE_NAME, ret
                );
        }
        return ret;
}
 
/* Add i8253_ref_device_initcall() to initialize function table. */
arch_initcall(i8253_ref_device_initcall);

kernel 初期化処理の時に呼ぶ関数を arch_initcall() で指定します。呼び出す順番を気にする場合は loader script (.lds) で厳密に制御する、既に存在する他の初期化処理コードを修正するなどして都合の良さそうな前後順になる様にして下さい。

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

platform device と platform driver の繋がり

次のコード片は 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 を参考に読んで見て下さい。

filei8253_ref_setup.c
Expand allFold all
 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
-
|
!
-
-
|
|
|
!
-
|
|
|
!
!
 
-
|
!
-
|
|
!
 
-
|
|
!
-
|
|
|
|
|
-
|
!
!
 
-
|
/* i8253 PIT Refresh counter and control port resource.
 * @note name(s) are compared with argument name of platform_get_resource_byname().
 */
static struct resource i8253_ref_ports[] = {
        {       .name =         I8253_REF_RESOURCE_REFRESH_COUNTER,
                .start =        PIT_CH1,
                .end =          PIT_CH1,
                .flags =        IORESOURCE_IO,
        },
        {       .name =         I8253_REF_RESOURCE_CONTROL_WORD,
                .start =        PIT_MODE,
                .end =          PIT_MODE,
                .flags =        IORESOURCE_IO,
        },
};
 
/*
 * Platform parameters will be passed to driver.
 */
static struct i8253_ref_platfrom_data i8253_ref_ch1 = {
        .channel =      1,      /*!< Refresh counter channel. */
        .rate_default = I8253_REF_RATE_DEFAULT_KEEP,    /*!< Initial rate value. */
};
 
/*
 * Define i8253 PIT channel 1, refresh counter as platform device.
 * 
 */
static struct platform_device i8253_ref_platform_device = {
        .name =          I8253_REF_DEVICE_NAME, /*!< device name match to driver name. */
        .id =            PLATFORM_DEVID_AUTO,   /*!< automatic id numbering. */
        .id_auto =       true,                  /*!< automatic id numbering. */
        .num_resources = ARRAY_SIZE(i8253_ref_ports),
        .resource =      i8253_ref_ports,
        .dev = {        /*!< struct device. */
                .platform_data = &i8253_ref_ch1, /*!< pass platform parameter to driver. */
        },
};
 
/*
 * Register i8253 PIT platform device.

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 に渡します。

filei8253_ref.h
Expand allFold all
 25
 26
 27
 28
 29
 
 
 
 
 
#define I8253_REF_DEVICE_NAME   "i8253_ref"
 
#define I8253_REF_RATE_DEFAULT_KEEP             (~(uint32_t)0)
#define I8253_REF_RESOURCE_REFRESH_COUNTER      "refresh_counter"
#define I8253_REF_RESOURCE_CONTROL_WORD         "control_word"

i8253_ref.c: i8253 をアクセスするドライバ

i8253_ref.c がソースコード全体です。

init

モジュールを初期化する処理から見ていきましょう。module_init() マクロに初期化処理関数 i8253_ref_init() を指定します。これは HelloWorld モジュールと同じです。printk() で関数が呼び出されたことを表示します。デバッグ目的です。platform_driver_register() で platform driver を登録します。

filei8253_ref.c
Expand allFold all
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
-
|
|
|
!
 
-
|
|
-
|
|
|
|
!
|
!
/*
 * Tutorial i8253 refresh platform device driver init.
 * @return int == 0: Success. 
 *             < 0:  Fail, negative errno number.
 */
static int __init i8253_ref_init(void)
{       int     ret;
        printk(KERN_INFO "%s: Called.\n", __func__);
        ret = platform_driver_register(&i8253_ref_driver);
        if (ret != 0) {
                /* Can not register driver. */
                printk(KERN_ERR "%s: Can not register driver. ret=%d\n",
                        __func__, ret
                );
        }
        return ret;
}
filei8253_ref.c
Expand allFold all
445
446
 
 
module_init(i8253_ref_init);
module_exit(i8253_ref_exit);

登録するドライバ i8253_ref_driver の内容を見てみましょう。platform_driver.device_driver driver.name に文字列 I8253_REF_DEVICE_NAME を指定しています。この文字列は platform_device_register で登録したデバイスの名前と照合され、一致していれば platform_driver の .probe に指定した関数 i8253_ref_probe() が呼ばれます。

filei8253_ref.c
Expand allFold all
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
-
|
!
 
-
|
|
!
 
 
-
|
!
-
-
|
|
|
|
!
|
|
|
!
/*
 * Suspend and resume power event handler table.
 */
#if (defined(CONFIG_PM))
struct dev_pm_ops i8253_ref_pm = {
        .suspend = i8253_ref_suspend,
        .resume = i8253_ref_resume,
};
#endif /* (defined(CONFIG_PM)) */
 
/*
 * i8253 PIT refresh counter platform driver structure.
 */
struct platform_driver i8253_ref_driver = {
        .driver = {
                .name = I8253_REF_DEVICE_NAME,  /*!< will be matched to device name. */
#if (defined(CONFIG_PM))
                .pm = &i8253_ref_pm,    /*!< power manage methods. */
#endif /* (defined(CONFIG_PM)) */
        },
        .probe = i8253_ref_probe,       /*!< device present or plugged. */
        .remove = i8253_ref_remove,     /*!< device or driver removed. */
        .shutdown = i8253_ref_shutdown, /*!< going halt. */
};

platform_driver のメソッド(関数テーブル)

platform_driver のメンバのいくつかは関数を指すポインタです。このページに出てきた関数を指すメンバ(メソッド)を次の表に示します。

メンバ変数変数が指した先の関数機能
probe対応するデバイスが見つかった(kernel に{登録されていたら | 登録されたら})呼び出されます。一般的なドライバはレジスタやデバイスのメモリをマップ(bus 直結のデバイス)、デバイスを存在確認、ドライバ状態領域確保、thread または work queue などの worker context 初期化・起動、割り込みハンドラ設置、初期化などをします。
remove対応するデバイスが外された(kernel から削除された)、または、ドライバが削除されたら呼び出されます。一般的なドライバはデバイスを停止する(外された場合はアクセスできないので注意が必要)、デバイスからの割り込みを止めてハンドラを外す、ドライバか起動した thread, work queue などの実行コンテキストを停止・終了する、デバイス・コンテキストを解放する、メモリマップ解除(bus 直結のデバイス)を行い probe 前の状態にします。
shutdownshutdown または reboot の時に呼び出されます。一般的なドライバはデバイスを停止する、デバイスからの割り込みを止めてハンドラを外す、thread, work queue などの実行コンテキストで継続中の処理を早期に終了する。電池動作機器の場合、デバイスに供給されている電源を遮断、クロックを停止などの追加的な停止処理を行う場合もあります。
driver->pm.suspendstate[1] = {freeze | standby | mem} に遷移する時に呼び出されます。一般的なドライバは電源供給停止、クロック停止または低周波数化などの電力管理、wakeup 準備、不意な割り込みを停止するなどを行います。
driver->pm.resumestate[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_driverdevice_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

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() からいくつかの関数を経由して呼ばれた様に見えます。

filei8253_ref.c
Expand allFold all
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 PIT refresh counter.
 * @param pdev points platform_device structure.
 * @return int 0:  Success, \
 *             <0: Error, negative errno number.
 */
int i8253_ref_probe(struct platform_device *pdev)
{       struct device                   *dev;
        struct resource                 *res;
        struct i8253_ref                *iref;
        struct i8253_ref_platfrom_data  *pdata;
        uint32_t                        rate_default;
        int                             ret;
 
        ret = 0;
        printk(KERN_INFO "%s: Called. pdev=0x%p, dev=0x%p\n",
                __func__, pdev, &(pdev->dev)
        );
        iref = kzalloc(sizeof(*iref), GFP_KERNEL);
        if (!iref) {
                printk(KERN_ERR "%s: Not enough memory.\n", __func__);
                ret = -ENOMEM;
                goto err;
        }
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_REFRESH_COUNTER);

probe 処理の順番はデバイスの挙動、確保するリソース同士の依存関係によって緻密に考える必要があります。i8253_ref ドライバではデバイス・コンテキスト i8253_ref を確保する処理 (kzalloc()) から始めました。次に resource から I/O ポート番号を取得して i8253_ref に保持する処理をします。メモリ・マップド・デバイスの場合は、物理メモリ空間を(kernel 内の)仮想アドレスへマップする処理 (例えば devm_ioremap_resource(), devm_request_mem_region(), ioremap(), ioremap_nocache() などを呼び出す処理) をする所です。

filei8253_ref.c
Expand allFold all
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
 
-
|
|
|
|
|
!
-
|
!
 
 
 
-
|
|
|
|
|
!
-
|
!
 
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_REFRESH_COUNTER);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_REFRESH_COUNTER
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->refresh_counter = res->start;
 
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_CONTROL_WORD);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_CONTROL_WORD
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->control_word = res->start;

platform_device_register() で登録したデバイスのパラメータを platform_data メンバが指す先から取り出します。都度ポインタを辿るのが面倒なのでドライバ状態 i8253_ref のメンバ変数にコピーを保持します。rate 初期値指定がある場合 (rate_default != I8253_REF_RATE_DEFAULT_KEEP) は分周比レジスタ(ここでは rate register と呼びます)へ値を設定します。rate register を読み出すことはできないので、ドライバ状態として rate_saved に書き込んだ値を保持します。

filei8253_ref.c
Expand allFold all
315
316
317
318
319
320
321
322
323
324
325
326
327
 
 
-
|
|
|
!
 
 
-
|
!
 
        dev = &(pdev->dev);
        pdata = dev->platform_data;
        if (!pdata) {
                printk(KERN_ERR "%s: Missing platform data.\n", __func__);
                ret = -ENODEV;
                goto err;
        }
        iref->channel = pdata->channel;
        rate_default = pdata->rate_default;
        if (rate_default != I8253_REF_RATE_DEFAULT_KEEP) {
                i8253_ref_rate_write(iref, rate_default);
        }
        iref->rate_saved = iref->rate_default = rate_default;

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

filei8253_ref.c
Expand allFold all
328
 
        dev_set_drvdata(dev, iref);

/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 に付いては後で詳しく触れます。

filei8253_ref.c
Expand allFold all
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
-
|
|
!
 
 
 
-
|
|
!
-
|
|
|
!
 
-
|
!
-
|
!
/*
 * Define device attributes.
 * device attribute appears as sysfs node in device directory.
 */
DEVICE_ATTR(counter, S_IRUGO, i8253_ref_counter_show, NULL);
DEVICE_ATTR(rate,    S_IWUSR | S_IRUGO, i8253_ref_rate_show, i8253_ref_rate_store);
 
/*
 *  Device attribute entries.
 *  @note DEVICE_ATTR creates structure dev_attr_##name
 */
static struct attribute *i8253_ref_attrs[] = {
        &dev_attr_counter.attr,
        &dev_attr_rate.attr,
        NULL,
};
 
/*
 * Device attribute group.
 */
static struct attribute_group i8253_ref_group = {
        .attrs = i8253_ref_attrs,
};

配列 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 ノードが作られます。

filei8253_ref.c
Expand allFold all
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
 
-
|
|
|
!
 
 
 
 
 
 
 
 
 
 
!
        ret = sysfs_create_group(&(dev->kobj), &i8253_ref_group);
        if (ret != 0) {
                dev_err(dev, "Can not create sysfs nodes.\n");
                dev_set_drvdata(dev, NULL);
                goto err;
        }
        dev_info(dev, "Probed. refresh_counter=0x%lx, control_word=0x%lx, channel=0x%x\n",
                iref->refresh_counter, iref->control_word, iref->channel
        );
        dev_info(dev, "Current state. rate_default=0x%lx, counter=0x%.4x\n",
                (unsigned long)rate_default, i8253_ref_counter_read(iref)
        );
        return ret;
err:
        kfree(iref);
        return ret;
}

ノードを作った直後から user space のアプリケーションからアクセスされる可能性が有ることに注意して下さい。デバイスの初期化が未完了だったり、定常動作に入るまで時間が掛かったり、ドライバが起動した thread や work queue などの worker が十分に処理可能な状態で無い場合、異常な値をアプリケーションに返したり、デバイスを異常状態に遷移させる可能性が無いようにして下さい。

remove

i8253_ref_remove() はデバイスが外された時 (platform_device_unregister()) または ドライバが外されようとした時 (platform_driver_unregister())に呼ばれます。このページの実装では platform_device_unregister() を呼び出す実装はないので、ドライバが外されようとした時の呼び出しに限定されます。

filei8253_ref.c
Expand allFold all
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
-
|
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
-
|
|
|
!
|
-
|
!
|
|
|
|
|
|
!
/*
 * Removed device or detach driver.
 * @param pdev points platform_device structure.
 * @return int 0:  Success, \
 *             <0: Error, negative errno number.
 */
int i8253_ref_remove(struct platform_device *pdev)
{       struct device           *dev;
        struct i8253_ref        *iref;
        int                     ret;
        uint32_t                rate_default;
 
        ret = 0;
        dev = &(pdev->dev);
        dev_info(dev, "Remove. dev=0x%p\n", dev);
        sysfs_remove_group(&(dev->kobj), &i8253_ref_group);
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Already removed.\n", __func__);
                ret = -ENODEV;
                goto out;
        }
        rate_default = iref->rate_default;
        if (rate_default != I8253_REF_RATE_DEFAULT_KEEP) {
                i8253_ref_rate_write(iref, rate_default);
        }
        /* Mark removed. */
        dev_set_drvdata(dev, NULL);
        /* Free driver context. */
        kfree(iref);
out:
        return 0;
}

sysfs_remove_group()DEVICE_ATTR(), sysfs_create_group() を使って作成したノードを削除しています。kfree() でデバイスのために確保したコンテキストを開放しています。一般的なドライバでは操作対象のデバイスが外されたか、ドライバが外されようとするので、割り込みハンドラ登録削除、デバイス停止処理(デバイスにアクセスできなくなっている場合もあるので注意)、デバイスのレジスタをアクセスするための物理アドレス-仮想アドレス割り付け解除、デバイスのために確保したメモリ領域を開放するなどの処理をします。

shutdown

i8253_ref_shutdown() は shutdown または reboot するときに呼びだされます。/sys/power/state に "disk" を書き込んで hybernate する場合は poweroff call back が呼び出されます。

filei8253_ref.c
Expand allFold all
381
382
383
384
385
386
387
388
389
390
-
|
|
!
 
-
|
|
|
!
/*
 * Shutdown or reboot kernel.
 * @param pdev points platform_device structure.
 */
void i8253_ref_shutdown(struct platform_device *pdev)
{       printk(KERN_INFO "%s: Called. pdev=0x%p, dev=0x%p\n",
                __func__, pdev, &(pdev->dev)
        );
        return;
}

i8253_ref ドライバは呼びだされたことを表示するだけの処理になっています。一般的なドライバでは次のことをします。

  • ドライバの therad や workqueue などで継続している処理をキャンセルして早期に終了させるか、最大で 100ms 程度で終了する見込みがあるのなら処理完了まで待つ。
  • デバイスの動作を停止する。DMA を使用して転送処理中であれば、キャンセルを試みる。
  • 割り込みを禁止または発生しないようにして、デバイスが電源 off 間際に予期しない動作を始めても、kernel の shutdown, reboot 処理を妨げないようにする。

    shutdown で確保したメモリ領域などを開放するべきか?

    開放せず、そのまま保持していて良いと考えています。remove が呼び出されていないので、kernel (/drivers/base) はデバイスもドライバも「存在している」という扱いになります。shutdown 以降、デバイスは機能せず read, write, その他の call back に対して -ENODEV などのエラー・コードを返すようにして、shutdown 間際で中途になる可能性がある処理を受け付けないようにするのが良いでしょう。

read and write

ドライバにとって必須の機能「デバイスをアクセスする」を見ていきます。レジスタを 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() を包んでいることが分かります。

read counter

次の 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() を使っています。

filei8253_ref.c
Expand allFold all
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
!
/*
 *  Read i8253 counter.
 *  @param  iref points driver state.
 *  @return uint16_t counter value.
 */
uint16_t i8253_ref_counter_read(struct i8253_ref *iref)
{       unsigned long   flags;
        uint16_t        counter;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LATCH
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        counter =  (inb_p(iref->refresh_counter)) << 0x0;
        counter |= (inb(iref->refresh_counter)) << 0x8;
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
        return counter;
}

outb_p() の実装はどこにあるの?

outb_p() のリンク先を辿っても、待ち処理が入った IO ポート出力処理は見つかりません。x86 と x86-64 アーキテクチャの場合はどこに実装されているのか? outb_p() の実装はどこにあるの? に探した時の記録をまとめてあります。Linux kernel ソース・コードを追っていくときの参考にもなるでしょう。

write rate

次の i8253_ref_rate_write() 関数は i8253 の rate (分周比)に書き込む処理です。手順は counter を読み出す処理とほぼ同様です。inb_p(), inb() 関数に代えて outb_p(), outb() を使用しています。

filei8253_ref.c
Expand allFold all
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
/*
 *  Write i8253 counter.
 *  @param iref points driver state.
 *  @param rate Refresh counter divider rate.
 */
void i8253_ref_rate_write(struct i8253_ref *iref, uint16_t rate)
{       unsigned long   flags;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LSB_MSB
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        outb_p((rate >> 0x0) & 0xff
                , iref->refresh_counter
        );
        outb((rate >> 0x8) & 0xff
                , iref->refresh_counter
        );
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
}

show and store

sysfs_create_group() で登録した counter, rate ノードの getter, setter 関数の実装を見ていきます。DEVICE_ATTR() マクロの第 3 引数が getter 関数です。慣習的に _show という接尾辞を付けます。第 4 引数が setter 関数です。慣習的に _store という接尾辞を付けます。

filei8253_ref.c
Expand allFold all
176
177
178
179
180
181
-
|
|
!
 
 
/*
 * Define device attributes.
 * device attribute appears as sysfs node in device directory.
 */
DEVICE_ATTR(counter, S_IRUGO, i8253_ref_counter_show, NULL);
DEVICE_ATTR(rate,    S_IWUSR | S_IRUGO, i8253_ref_rate_show, i8253_ref_rate_store);

_show() - read() system call に対応する処理

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 に返す文字列を伸ばしたり、領域の伸長が行われないことも確認できます。

filei8253_ref.c
Expand allFold all
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
|
!
/*
 *  counter node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_counter_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        return snprintf(buf, PAGE_SIZE, "%u\n", (unsigned)i8253_ref_counter_read(iref));
}

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 のアプリケーションを作る人と相談して決めるのが良いでしょう。システム・コール・エラーで返すのが良いのか、特別な意味を持つ文字列で返すのか。都合の良い方法を選択してください。

filei8253_ref.c
Expand allFold all
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
-
|
|
!
|
!
/*
 *  rate node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_rate_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        if (iref->rate_saved == I8253_REF_RATE_DEFAULT_KEEP) {
                /* Read back value is not available. */
                return snprintf(buf, PAGE_SIZE, "-1\n");
        }
        return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)(iref->rate_saved));
}

_store() - write() system call に対応する処理

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 となります。

filei8253_ref.c
Expand allFold all
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
-
|
|
|
|
|
|
|
|
!
 
 
 
-
|
|
|
|
-
|
|
!
|
|
-
|
|
!
-
|
|
!
-
|
|
!
|
|
|
!
/*
 *  rate node: store (user does write())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer filled with write stream.
 *  @param count write stream bytes in buffer.
 *  @return ssize_t >=0: bytes read from buffer, \
 *                  <0: error, negative errno number.
 *  @note kernfs/sysfs terminates write stream by '\0'.
 */
ssize_t i8253_ref_rate_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{       char                    *p2;
        unsigned long           rate;
        struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        p2 = NULL;
        rate = simple_strtoul(buf,&p2,0);
        if (!p2) {
                /* simple_strtoul doesn't work. */
                return -EINVAL;
        }
        if ((unsigned)(*p2) >= ' ') {
                /* Terminated with some invalid character. */
                return -EINVAL;
        }
        if (rate > 0xffff) {
                /* Too large value. */
                return -EINVAL;
        }
        i8253_ref_rate_write(iref, rate);
        iref->rate_saved = rate;
        return (__force ssize_t)count;
}

文字列から整数に変換する関数は /include/linux/kernel.h で定義されています。libc に有る関数と似たような名前の関数が用意されています。simple_strtoul(), kstrtoul(), sscanf() などがあります。user space から渡されたパラメータは十分に定義域のチェック、受け取り可能な長さの制限を施して下さい。パラメータに複雑な文法があり、それをパースする場合はどんなパターンであっても無限ループに入らず、関数が return する様にして下さい。

simple_strtoul() は obsolete ですか...

"This function is obsolete. Please use kstrtoul instead."

suspend and resume

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

suspend

i8253_ref_suspend() はデバイスを低消費電力状態に遷移させる必要があるときに呼び出されます。i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。

filei8253_ref.c
Expand allFold all
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
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
|
!
/*
 * Handle event suspend.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_suspend(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
 
out:
        dev_info(dev, "Suspended. counter=0x%.4x\n", counter);
        return 0;
}

一般的なドライバで suspend 処理で実装される内容は次の通りです。

  • 一連の制御シーケンスを伴うデバイス制御をしている場合は、それが完了するまで待つか処理をキャンセルする。
  • 割り込みを禁止するか、デバイスが wakeup 機能を持っているならば割り込みを wakeup 信号として機能するように設定する。割り込みが発生した後、 thread や workqueue などを使って割り込み応答処理を「始めようとしている」、あるいは「始まっている」場合の動作を注意深く実装して下さい。
  • デバイスを低消費電力状態に遷移させる: クロック周波数を低くするあるいは停止する、電力消費が大きい回路ブロックを停止する。

機器全体の機能として CPU が停止しても、周辺デバイスを動作させ、ユーザーに対して何らかの働きかけを続ける、あるいは機器外部との関係を続けるような仕様の場合はそれに準じた設定をします。

i8253 でも僅かな低消費電力化は可能かもしれない

PC-AT 互換機の回路構成で使われている i8253 channel 1 はカウンタを止める mode5 があります。mode5 に設定すれば counter FF の状態遷移が止まるので僅かに低消費電力化が可能かもしれません。

resume

i8253_ref_resume() はデバイスを低消費電力から復帰させる必要があるときに呼び出されます。resume の実装同様に i8253 は低消費電力状態に移行する機能はないので、カウンタを読み出すだけの動作を実装してあります。

filei8253_ref.c
Expand allFold all
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
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
!
/*
 * Handle event resume.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_resume(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
out:
        dev_info(dev, "Resumed. counter=0x%.4x\n", counter);
        return 0;
}

exit

i8253_ref_exit() は i8253_ref.ko モジュールが kernel から rmmod されるときに呼び出されます。関数に付いている __exit 修飾子はドライバを kernel に static link する時は、kernel image に含めない様に指定する修飾です。

filei8253_ref.c
Expand allFold all
435
436
437
438
439
440
441
442
443
-
|
|
!
 
-
|
|
!
/*
 * Tutorial i8253 refresh platform device driver exit (removing module).
 * @return void
 */
static void __exit i8253_ref_exit(void)
{       printk(KERN_INFO "%s: Called.\n", __func__);
        /* platform_driver_unregister will call _remove driver method. */
        platform_driver_unregister(&i8253_ref_driver);
}

platform_driver_unregister() を呼び出しドライバの登録を解除します。platform_driver_unregister() を呼び出すと remove call back i8253_ref_remove() が呼び出されます。remove 関数の中でデバイスを機能停止し、probe() で確保した領域、割り込み設定(i8253_ref ドライバでは割り込みは実装されていません)、デバイスアクセス用のノードなどを解放し、kernel から取得した資源を返します。モジュール全体で確保した資源があるならば、ドライバ登録を解除した後の方が都合が良いでしょう。i8253_ref.ko モジュールはモジュール全体で確保した資源が無いので特に何もしていません。

まとめ

次はこのページに出てきた主な項目です。並びはページの構成と変えて整理してあります。


添付ファイル: filei8253_ref.tar.gz 143件 [詳細] filei8253_ref_data_structure.png 425件 [詳細] fileplatform_device.xlsx 152件 [詳細] filei8253_ref_light_blue_color.png 270件 [詳細] filei8253_ref.c 360件 [詳細] filei8253_ref_light_orange_color.png 273件 [詳細] fileMakefile 187件 [詳細] filei8253.png 461件 [詳細] filei8253_ref_setup.c 326件 [詳細] filei8253.xlsx 90件 [詳細] filei8253_ref.h 296件 [詳細] filebus_tree_to_i8253.png 398件 [詳細] filei8253_control.h 221件 [詳細] fileKconfig 110件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-11-03 (金) 22:05:48 (2368d)